Skip to content

Instantly share code, notes, and snippets.

@qiscus
Created August 19, 2014 23:59
Show Gist options
  • Save qiscus/273b5ca8964a4efd4ee7 to your computer and use it in GitHub Desktop.
Save qiscus/273b5ca8964a4efd4ee7 to your computer and use it in GitHub Desktop.
<?php
// this script was created by Akbar Taufiq Herlangga (Software Engineer at Qiscus)
// this script was created for the Qiscus TechTalk #35 - Build Your Own MVC Framework
//////////////////////////////////////////////////////////////////////
//////////////////////////// M O D E L ///////////////////////////////
class MovieTicket
{
private $seat;
private $price;
public function __construct($seat, $price)
{
$this->seat = $seat;
$this->price = $price;
}
public function toArray() {
$representation = array();
$representation['seat'] = $this->seat;
$representation['price'] = $this->price;
return $representation;
}
public function getSeat() { return $this->seat; }
public function getPrice() { return $this->price; }
}
class MovieScreening
{
private $movieCode;
private $movieName;
private $screeningDate;
private $tickets;
/**
* @var array An associative array containing Ticket ID as the key
* and boolean value that represent availability for that ticket
* as the value.
*/
private $availabilities = array();
public function __construct($movieCode, $movieName, DateTimeImmutable $screeningDate, array $tickets, array $availabilities = array())
{
$this->movieCode = $movieCode;
$this->movieName = $movieName;
$this->tickets = $tickets;
$this->screeningDate = $screeningDate;
$this->availabilities = $availabilities;
if (empty($this->availabilities)) {
// Mark all tickets as available
foreach ($tickets as $ticket) {
$this->availabilities[$ticket->getSeat()] = true;
}
}
}
public function book(MovieTicket $ticket)
{
// Check whether the ticket really belongs to this MovieScreening.
if ( ! in_array($ticket, $this->tickets)) {
return false;
}
// Make sure that the ticket still available.
if ( ! $this->availabilities[$ticket->getSeat()]) {
return false;
}
$this->availabilities[$ticket->getSeat()] = false;
return true;
}
public function getTicket($seat)
{
foreach ($this->tickets as $ticket) {
if ($ticket->getSeat() == $seat) {
return $ticket;
}
}
return null;
}
public function getAllTickets()
{
return $this->tickets;
}
public function getBookableTickets()
{
$availabilities = $this->availabilities;
// FUNCTIONAL STYLE FTW!
return array_reduce($this->tickets, function($result, $ticket) use ($availabilities) {
if ($availabilities[$ticket->getSeat()]) {
$result[] = $ticket;
}
return $result;
}, array());
}
public function getBookedTickets()
{
$availabilities = $this->availabilities;
// FUNCTIONAL STYLE FTW!
return array_reduce($this->tickets, function($result, $ticket) use ($availabilities) {
if ( ! $availabilities[$ticket->getSeat()]) {
$result[] = $ticket;
}
return $result;
}, array());
}
public function toArray()
{
$representation = array();
$representation['movieCode'] = $this->movieCode;
$representation['movieName'] = $this->movieName;
$representation['screeningDate'] = $this->screeningDate->format('Y-m-d');
$representation['bookableTickets'] = array_reduce($this->getBookableTickets(), function($result, $ticket) {
$result[] = $ticket->toArray();
return $result;
}, array());
$representation['bookedTickets'] = array_reduce($this->getBookedTickets(), function($result, $ticket) {
$result[] = $ticket->toArray();
return $result;
}, array());
return $representation;
}
public function getMovieCode() { return $this->movieCode; }
public function getMovieName() { return $this->movieName; }
public function getSeat() { return $this->seat; }
public function getScreeningDate() { return $this->screeningDate; }
}
interface MovieScreeningRepositoryInterface
{
public function find($id);
public function findMoviesAfter(DateTimeImmutable $date);
public function findMoviesBetween(DateTimeImmutable $begin, DateTimeImmutable $end);
public function save(MovieScreening $movieScreening);
}
//////////////////////////////////////////////////////////////////////
///////////////////////////// V I E W ////////////////////////////////
/**
* MovieScreening viewer.
*/
interface MovieScreeningViewInterface
{
/**
* View a single MovieScreening.
*
* @param MovieScreening $movieScreening A MovieScreening to be viewed.
*/
public function renderMovieScreening(MovieScreening $movieScreening);
/**
* View multiple MovieScreening.
*
* @param array $movieScreening A collection of ovieScreening to be viewed.
*/
public function renderMultipleMovieScreenings(array $movieScreenings);
}
//////////////////////////////////////////////////////////////////////
/////////////////////// C O N T R O L L E R //////////////////////////
class MovieScreeningController
{
private $movieScreeningRepository;
private $movieScreeningView;
public function __construct(
MovieScreeningRepositoryInterface $movieScreeningRepository,
MovieScreeningViewInterface $movieScreeningView)
{
$this->movieScreeningRepository = $movieScreeningRepository;
$this->movieScreeningView = $movieScreeningView;
}
public function index()
{
$today = new DateTimeImmutable("now", new DateTimeZone("Asia/Jakarta"));
$upcomingMovieScreenings = $this->movieScreeningRepository->findMoviesAfter($today);
$this->movieScreeningView->renderMultipleMovieScreenings($upcomingMovieScreenings);
}
public function show($id)
{
$movieTicket = $this->movieScreeningRepository->find($id);
$this->movieScreeningView->renderMovieScreening($movieTicket);
}
}
//////////////////////////////////////////////////////////////////////
///////////////// I N F R A S T R U C T U R E ////////////////////////
class FileMovieScreeningRepository implements MovieScreeningRepositoryInterface
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
public function find($movieCode)
{
$movieScreenings = $this->findAll();
$matchedMovieScreenings = array_values(array_filter($movieScreenings, function($movieScreening) use ($movieCode) {
if ($movieScreening->getMovieCode() == $movieCode) {
return true;
}
return false;
}));
if ( ! empty($matchedMovieScreenings)) {
return $matchedMovieScreenings[0];
}
return null;
}
public function findMoviesAfter(DateTimeImmutable $date)
{
$movieScreenings = $this->findAll();
return array_values(array_filter($movieScreenings, function($movieScreening) use ($date) {
if ($movieScreening->getScreeningDate() > $date) {
return true;
}
return false;
}));
}
public function findMoviesBetween(DateTimeImmutable $begin, DateTimeImmutable $end)
{
$movieScreenings = $this->findAll();
return array_values(array_filter($movieScreenings, function($movieScreening) use ($begin, $end) {
$movieScreeningDate = $movieScreening->getScreeningDate();
if ($movieScreeningDate > $begin && $movieScreeningDate < $end) {
return true;
}
return false;
}));
}
public function save(MovieScreening $newMovieScreening)
{
$movieScreenings = $this->findAll();
$movieScreenings[] = $newMovieScreening;
// Remove duplicated values
// FUNCTIONAL STYLE FTW!
$uniqueMovieScreenings = array_values(array_reduce($movieScreenings, function($result, $element) {
$result[$element->getMovieCode()] = $element;
return $result;
}, array()));
file_put_contents($this->path, serialize($uniqueMovieScreenings));
}
private function findAll()
{
$fileContents = file_get_contents($this->path);
$movieScreenings = unserialize($fileContents);
return $movieScreenings;
}
}
//////////////////////////////////////////////////////////////////////
/////////////////// U S E R I N T E R F A C E /////////////////////
class JsonMovieScreeningView implements MovieScreeningViewInterface
{
public function renderMovieScreening(MovieScreening $movieScreening)
{
header('Content-type: application/json');
echo json_encode($movieScreening->toArray());
}
public function renderMultipleMovieScreenings(array $movieScreenings)
{
header('Content-type: application/json');
$array = array();
foreach($movieScreenings as $movieScreening) {
$array[] = $movieScreening->toArray();
}
echo json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
}
class XmlMovieScreeningView implements MovieScreeningViewInterface
{
public function renderMovieScreening(MovieScreening $movieScreening)
{
header('Content-type: text/xml');
$movieScreeningXml = new SimpleXMLElement('<movie_screening />');
static::toXml($movieScreeningXml, $movieScreening->toArray());
echo $movieScreeningXml->asXml();
}
public function renderMultipleMovieScreenings(array $movieScreenings)
{
header('Content-type: text/xml');
$array = array();
foreach($movieScreenings as $movieScreening) {
$array[]['movieScreening'] = $movieScreening->toArray();
}
$movieScreeningsXml = new SimpleXMLElement('<movie_screenings />');
static::toXml($movieScreeningsXml, $array);
echo $movieScreeningsXml->asXML();
}
private static function toXml(SimpleXMLElement $element, array $data) {
foreach ($data as $key => $value) {
if (is_numeric($key)) {
$key = array_keys($value)[0];
$value = $value[$key];
}
if (is_array($value)) {
$newElement = $element->addChild($key);
static::toXml($newElement, $value);
}
else {
$element->addChild($key, $value);
}
}
}
}
class HtmlMovieScreeningView implements MovieScreeningViewInterface
{
public function renderMovieScreening(MovieScreening $movieScreening)
{
header('Content-type: text/html');
echo <<<RAW
<html>
<head>
</head>
<body>
<div class="movie_screening">
<span class="movie_screening_name">{$movieScreening->getMovieName()}</span>
<span class="movie_screening_date">{$movieScreening->getScreeningDate()->format('d M Y')}</span>
RAW;
foreach ($movieScreening->getBookableTickets() as $bookableTicket) {
}
echo <<<RAW
</div>
</body>
</html>
RAW;
}
public function renderMultipleMovieScreenings(array $movieScreenings)
{
header('Content-type: text/html');
echo <<<RAW
<html>
<head>
</head>
<body>
RAW;
foreach ($movieScreenings as $movieScreening) {
echo <<<RAW
<div class="movie_screening">
<span class="movie_screening_name">{$movieScreening->getMovieName()}</span>
<span class="movie_screening_date">{$movieScreening->getScreeningDate()->format('d M Y')}</span>
</div>
RAW;
}
echo <<<RAW
</body>
</html>
RAW;
}
}
//////////////////////////////////////////////////////////////////////
/////////////////// A P P C O N T R O L L E R //////////////////////
$headers = $_SERVER;
$request = $_REQUEST;
/*
* Determine the requested controller and action
*/
$requestUri = $headers['REQUEST_URI'];
$pathAndParams = explode("?", $requestUri);
$path = $pathAndParams[0];
$params = isset($pathAndParams[1]) ? $pathAndParams[1] : array(); // We're using $request instead of $params for the moment
$controllerAndAction = explode("/", $path);
$requestedController = $controllerAndAction[1];
$requestedAction = isset($controllerAndAction[2]) ? $controllerAndAction[2] : "index";
$requestedMethod = $headers['REQUEST_METHOD'];
/*
* Determine the requested output
*/
$acceptance = $headers['HTTP_ACCEPT'];
$requestedOutput = stristr($acceptance, "application/json") ? "json" : (stristr($acceptance, "text/xml") ? "xml" : "html");
/*
* Instantiate the infrastructure
*/
$movieScreeningRepository = new FileMovieScreeningRepository('movie_screenings.db');
/*
* FINALLY: Start the engine..
*/
switch($requestedController) {
case "movie_screen": {
switch ($requestedOutput) {
case "json":
$movieScreeningView = new JsonMovieScreeningView();
break;
case "xml":
$movieScreeningView = new XmlMovieScreeningView();
break;
case "html":
$movieScreeningView = new HtmlMovieScreeningView();
break;
default:
$movieScreeningView = new HtmlMovieScreeningView();
break;
}
$controller = new MovieScreeningController($movieScreeningRepository, $movieScreeningView);
switch($requestedAction) {
case "index":
$controller->index();
break;
case "show":
$movieTicketId = $request["movie_code"];
$controller->show($movieTicketId);
break;
default:
$controller->index();
break;
}
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment