MVC Refactor, Take 2

And to take this example further still, let’s take the same idea and extend it so that the user can select a city from a <select> and it will display the time in that city.

Using Web MVC:

clock.html.php


	<form action="/clock" method="post">
	<label>Select a city</label>
	<select name="timezone" onchange="this.form.submit();">
	<?php foreach ($timezones as $timezone => $cityName) {
	?>
	<option value="<?= $timezone; ?>" <?= (isset($city) && $city === $cityName ? 'selected="selected"' : ''); ?><?= $city; ?></option>
	<? } ?>
	</select>
	</form>

<?php
if (isset($city)) {
	?>
	The time in <em><?= $city; ?></em> is <strong><?= $time; ?></strong>
	<?php
}
?>

clockmodel.php

class ClockModel implements FormModel {
		
	public $timezones = ['Europe/London' => 'London', 
						 'America/New_York' => 'New York', 
						 'America/Los_Angeles', 'Los Angeles'];

	public function getTime($timezoneStr) {
		$timezone = new DateTimeZone($timezone);
		$date = new DateTime;
		$date->setTimeZone($timezone);
		return $date;
	}

	public function isValid($timezone) {
		return isset($this->timezones[$timezone]);
	}

	public function getCity($timezone) {
		return $this->timezones[$timezone];
	}
}

clockcontroller.php


class ClockController {
	private $template;

	public function __construct(Template $template) {
		$this->template = $template;
	}

	public function viewAction() {
		$model = new ClockModel;
		return ['headers' => '', 'body' => $this->template->render('clock.html.php', ['timezones' => $model->timezones]);
	}

	public function submitAction($post) {
		$model = new ClockModel;

		if ($model->isValid($post)) {
			return ['headers' => '', 'body' => $this->template->render('clock.html.php', 
													['timezones' => $model->timezones, 
													'city' => $model->getCity($post['timezone']), 
													'time' => $model->getTime($get['timezone'])]);
		}
		else return $this->viewAction();
		
	}
}

index.php

else if ($_SERVER['REQUEST_URI'] === '/clock' && $_SERVER['REQUEST_METHOD'] === 'GET') {
	$controller = new ClockController(new Template);
    $response = $controller->viewAction();
}
else if ($_SERVER['REQUEST_URI'] === '/clock' && $_SERVER['REQUEST_METHOD'] === 'POST') {
	$controller = new ClockController(new Template);
    $response = $controller->submitAction($_POST);
}

By Comparison, in TomVC

I’m going to assume the framework provides this interface and class, they’re basically the ones I presented earlier but previously used Jeff’s less generic function names.

interface FormModel {
	public function submit($data);
	public function isValid($data);
	public function success();
}


class FormController {
	private $model;

	public function __construct(FormModel $model) {
		$this->model = $model;
	}

	public function submit($data) {
		if ($this->model->isValid($data)) {
			if ($this->model->submit($data)) {
				$this->model->success();
			}
		}
	}
}

So the developer would provide:

class ClockModel implements FormModel {
		
	public $timezones = ['Europe/London' => 'London', 
						 'America/New_York' => 'New York', 
						 'America/Los_Angeles', 'Los Angeles'];

	public $timezone;
	public $submitted = false;
	

	public function isValid($post) {
		return isset($this->timezones($post['timezone']));
	}

	public function submit($data) {
		$this->timezone = $data['timezone'];
	}

	public function success() {
		$this->submitted = true;
	}

	public function getTime() {
		$timezone = new DateTimeZone($this-timezone);
		$date = new DateTime;
		$date->setTimeZone($timezone);
		return $date;
	}

	public function getCity() {
		return $this->timezones[$this->timezone];
	}
}

clock.html.php


	<form action="/clock" method="post">
	<label>Select a city</label>
	<select name="timezone" onchange="this.form.submit();">
	<?php foreach ($model->timezones as $timezone => $city) {
	?>
	<option value="<?= $timezone; ?>" <?= $model->timezone === $timezone ? 'selected="selected"' : ''); ?><?= $city; ?></option>
	<? } ?>
	</select>
	</form>

<?php
if ($model-submitted) {
	?>
	The time in <em><?= $model->getCity(); ?></em> is <strong><?= $time; ?></strong>
	<?php
}
?>

index.php

else if ($_SERVER['REQUEST_URI'] === '/clock' && $_SERVER['REQUEST_METHOD'] === 'GET') {
	$model = new ClockModel;
	$controller = new ClockController($model);
    $view = new View('clock.html.php', $model);
}
else if ($_SERVER['REQUEST_URI'] === '/clock' && $_SERVER['REQUEST_METHOD'] === 'POST') {
	$model = new ClockModel;
	$controller->submitAction($post);
    $view = new View('clock.html');
}

And just to be clear, the router could be a lot smarter than this since there’s a finite number of reusable controllers:


$name = trim($_SERVER['REQUEST_URI'], '/');

if (class_exists($name . 'model')) {
	$model = new ($name . 'model');
	if ($model instanceof FormModel) $controller = new FormController($model);
	if (file_exists($name . '.html.php')) $view = new View($name . '.html.php', $model);
}

if ($controller instanceof FormController) {
	if ($_SERVER['REQUEST_METHOD'] == 'POST') {
		$controller->submitAction($_POST);
	}
}


And only require explicit configuration when convention isn’t used.

In OOP imagine this being set up to be extensible using something like

$router->registerType('FormController', 'FormModel', function() {
	if ($_SERVER['REQUEST_METHOD'] == 'POST') {
		$controller->submitAction($_POST);
	}
});

In which case, the user wouldn’t even need to supply a route. I realise this is possible with web mvc as well but just wanted to demonstrate that it’s not exclusive to it.