MVC in PHP: Deploying MVC on the web
MVC on the web
In part 1: Hello World in MVC I provided a basic example of implementing a standard MVC set up in PHP. The biggest problem with this standard approach on the web is scalability. In the examples, I have explicitly stated which model, view and controller are initiated. Which is fine in the desktop application environment where it was conceived. On the web, however, there are some constraints imposed by the underlying architecture.
A reasonable request would be to substitute the model being used. How can we do this on the web? Have a specific PHP file for each triad, initialising all 3 components? It's certainly an option, but ties you down to a URI structure and specific PHP files for each task. This method produces a lot of repeated code.
For example, what about the very common scenario of putting the MVC triad inside an overall site layout? It would require even more repeated layout generation code in each page. What if this layout code has to be updated? That's a lot of places to change it! Obviously this isn't viable in any non-trivial project.
How, then, can the script know which model view and controller are needed?
Initiating the triad
The first, and biggest, issue with MVC on the web is correctly initiating each MVC triad. How can re-usability of the components be ensured while reducing repeated initiation code?
One option is to provide all the required parameters in the URI:
site.com/mvc.php?model=model&view=view&controller=controller
This, while functional, is very messy and creates verbose URLs. For these reasons, along with other considerations such as SEO, user friendliness and the security implications of allowing anyone to load any model into any view prevents this from being anywhere near viable in the real world.
Another option is to make the assumption that each triad is a matched set. e.g.
site.com/search
could initiate SearchModel, SearchView and SearchController
Again, this would certainly work, but any potential re-usability of any of the components is compromised. To re-use the view in, for example, "UserSearch" an empty class would have to be created which simply extended SearchView which is, at best, redundant, creates clutter and makes it difficult for anyone looking at the code to locate where something is actually happening.
What's needed is a single entry point which:
- Takes the user's request
- Initiates the correct Model, View and Controller
- Passes any user actions to the relevant controller
- Is then able to place the output of the MVC triad's view into an overall layout
This single entry point is commonly known as a "Front Controller" (Fowler 2002; Sun Microsystems, 2001). Even with a front controller, the problem remains: how can the front controller know which components to initiate?
What is the best way to achieve this? Either there needs to be a unique entry point script for each triad which, as I have discussed, causes as many problems as it solves or there needs to be a front controller. The front controller is certainly the better option as it gives much greater consistency and control over the containing code (e.g. layout generation) and enforces shared initialisation code allowing for strict input sanitisation, consistent controller action routing and layout configuration.
But the problem remains, how can the front controller know which model, view and controller to initialise?
There's no simple way it can.
Static or Dynamic routing
The most complete solution is to create a database (or data structure) containing all possible routes and a script to deal with the request, look it up the route and initiate the triad. I will refer to this as Static routing For example:
$actions = array(
'search' => array('model' => 'SearchModel', 'view' => 'SearchView', 'controller' => 'SearchController'),
'userEdit' => array('model' => 'UserModel', 'view' => 'EditUserView', 'controller' => 'EditUserController')
);
While this is probably the most complete solution, it does have several disadvantages:
- From a scalability point of view, this could potentially become huge, creating both performance issues and maintainability ones. For a developer to work out which classes are being used they need to go through a code trail and find the correct route in the database.
- Additional development time. It won't take long but I'm a huge advocate of convention over configuration and the avoidance of configuration files wherever possible.
- Potential for inconsistency. There is zero real relationship between the URI and the components being used. It's possible a URI of /search could map to components called "User" or "Product", although this is also a good thing as it adds flexibility, it does also increase complexity.
- Loss of easy portability. There's no way to copy components between projects and have them just work. A route must be added for them.
Another solution is to delegate the job of component creation to one of the existing components. Models should be shared and know nothing of their views and controllers so putting this logic here makes no sense. Views could create controllers but this would remove their reusability. The controller is the logical location for this as each controller is related to one model and one view and designed to handle one task. Controllers are tied to a specific view implementation so having the controller create the view has a minimal impact. It does, however, limit flexibility and means ugly brachning and conditionals to allow support for different views. The real problem is that on one hand, once you start giving controllers more responsibilities because they're not reusable those responsibilities become be a candidate for reuse and a reusable controller becomes desirable again.
With this method (which I shall refer to as Dynamic routing) the route is worked out at runtime based on a "best match" approach where the route is worked out based on whether relevant classes/methods exist within the system. This allows a much more flexible URI scheme something I plan to address in detail in another article but with dynamic routing the front controller can decide what to initiate based on what's available in the system (and/or filesystem).
This does, however, deviate from the standard MVC pattern by giving the controller an extra responsibility
This way, the URI can be structured as
frontcontroller.php?controller=controller&action=controlleraction
Several of the popular frameworks work in a similar way to this (although often take it a step further)
The program flow will now be:
- The front controller is initialised
- The front controller creates the specified controller
- The controller creates its model and view (which are hard-coded into it)
- The front controller calls the specified action on the controller
- The front controller gets the view from the controller and returns its output
Although this is how a lot of the major frameworks work, the problem with this approach is it adds extra responsibilities to the controller. A lot of the popular frameworks force view selection in the controller but this limits reuse of the controllers and means that a view cannot exist without a controller. Doing something as simple as showing some HTML now requires initiating an entire MVC triad even if there are no user actions.
Both Static and Dynamic routing are adequate for the job. However, Controller-centric Dynamic routing (as described above) does deviate from the MVC pattern by putting more responsibilities in the controller and severly limits flexibility. By putting View selection or Model selection in the Controller it makes it impossible to use the Controller with a different view, even though the view may just be a slight variation with identical actions, the whole controller and view need to be supplied again.
The controller:view relationship is 1:1
The big mistake a lot of implementations, especially in the PHP world, make is making controller actions create (or at least select) their views. Should each controller action create its own view?
As I already mentioned, several frameworks take it further and allow (sometimes even force) view selection on a per-action basis. For example:
class UserController extends Controller {
public function showList() {
$users = $this->model->findAll();
$this->set('users', $users);
$this->render('list.tpl');
}
public function sortList($order) {
$users = $this->model->setSort($order);
$this->set('users', $users);
$this->render('list.tpl');
}
public function edit($id) {
$user = $this->model->findById($id);
$this->set('user', $user);
$this->render('form.tpl');
}
public function save() {
if ($this->model->save($_POST)) {
$this->render('success.tpl');
}
else {
$user = $this->model->findById($_POST['id']);
$this->set('user', $_POST);
$this->render('form.tpl');
}
}
}
If you've studied at a lot of the popular MVC frameworks this will look familiar. What's wrong with it? Firstly, the controller now has two responsibilities: editing and listing users, causing unnecessary repeated code. The other result of this is that because the view could be anything it forces binding logic into the controller which is bad
On the web there is no reason at all that the controller even needs to know about the view. In desktop applications, the controller must be able to inform the view to refresh after a user-action. On the web, the view is always refreshed anyway so this entire relationship is irrelevant.
By completely decoupling the view from the controller, there are several advantages:
- The view selection logic exists outside the controller adding increased flexibility. The controller doesn't care at all about what the view is doing, or even if it exists.
- The binding logic in the controller can be removed
- Each controller now has zero knowledge for the view. This is good because the view can then be substituted without making any changes to the controller
- Smaller, simpler controllers with less repeated code. The controller should never call render or select a view.
For example. The above code for editing and listing users can be simplified and split into smaller more easily reusable classes:
class UserListController{
private $model;
public function __construct(UserModel $model) {
$this->model = $model;
}
public function sort($order){
$this->model->setSort($order);
}
}
class UserEditController {
public function __construct(UserEditModel $model) {
$this->model = $model;
}
public function main($id) {
$this->model->load($id);
}
public function save() {
$this->model->save($_POST);
}
}
For completeness, here are the related views and models:
class UserEditModel {
private $pdo;
private $user;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function load($id) {
$stmt = $this->pdo->prepare('SELECT * FROM user WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch();
}
public function getUser() {
return $this->user;
}
}
class UserEditView {
private $model;
public function __construct(UserEditModel $model) {
$this->model = $model;
}
public function render() {
$user = $this->model->getUser();
return '<h2>Editing user ' . $user->name . '</h2>
<form>......</form>';
}
}
class UserListModel {
private $pdo;
private $sort = 'name ASC';
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function setSort($sort) {
$this->sort = $sort;
}
public function findAll() {
$stmt = $this->pdo->prepare('SELECT * FROM user ORDER BY ' . $this->sort);
$stmt->execute();
return $stmt->fetchAll();
}
}
class UserListView {
private $model;
public function __construct(UserListView $model) {
$this->model = $model;
}
public function render() {
$html = '<h2>User List</h2>';
$html . = '<ul>'
//loop through users an generate a HTML table.
foreach ($this->model->findAll() as $user) $html .= '<li>' . $user->name . '</li>';
$html .= '</ul>';
return $html;
}
}
What does this achieve in practical terms? Modularity. Any part of any triad can be initiated independently.
There is no need to call a "set up" action on the controller in order for it to choose a view and the responsibility of each component is a lot clearer. This is an advantage because it makes both the controller and the component which is calling it (in this case, the front controller) simpler. The controller is now free of state and the application state (things like "Which ")
$module = new UserController;
$controller->showList(); //this would be redundant if the controller was related to a single view.
echo $controller->output();
In the first example, if the controller object was being passed around its state would be ambiguous. Any method receiving the UserController object would be unsure whether the view was initialised, and if it was whether it was for listing users or editing them. This is poor from an OOP perspective because it breaks encapsulation: the front controller is aware of the controller's internal state.
Creating a front controller
For this example I will use Static Routing because it's simpler to demonstrate and allows for a stricter MVC approach. The main thing to notice is that the relationship is that the relationship between the instances is 1:1:1. That is, there is one controller instance, one model instance and one view instance per route. However, they are entirely polymorphic. I can use any view with any controller and any model.
To incorporate the front controller into the Hello World example from part one, firstly the front controller must be created:
class FrontController {
private $controller;
private $view;
public function __construct(Router $router, $routeName, $action = null) {
//Fetch a route based on a name, e.g. "search" or "list" or "edit"
$route = $router->getRoute($routeName);
//Fetch the names of each component from the router
$modelName = $route->model;
$controllerName = $route->controller;
$viewName = $route->view;
//Instantiate each component
$model = new $modelName;
$this->controller = new $controllerName($model);
$this->view = new $viewName($routeName, $model);
//Run the controller action
if (!empty($action)) $this->controller->{$action}();
}
public function output() {
//Finally a method for outputting the data from the view
//This allows for some consistent layout generation code such as a page header/footer
$header = '<h1>Hello world example</h1>';
return $header . '<div>' . $this->view->output() . '</div>';
}
}
This uses a very simplistic router which maps a route, that is a string that has come in as part of the URL, to a model, view and controller. The routing table looks like this:
*In the real world, the front controller would not contain the layout HTML itself. For the simplicity of demonstration I have put this here, what does need to happen here is the insertion of the specified triad's output into the generic layout.
class Router {
private $table = array();
public function __construct() {
//"exampleroute" is the name of the route, e.g. /exampleroute
//Here, class names are used rather than instances so instances are only ever created when needed, otherwise every model, view and
//controller in the system would be instantiated on every request, which obviously isn't good!
$this->table['exampleroute'] = new Route('Model', 'View', 'Controller');
$this->table['someotherroute'] = new Route('OtherModel', 'OtherView', 'OtherController');
}
public function getRoute($route) {
$route = strtolower($route);
return $this->table[$route];
}
}
class Route {
public $model;
public $view;
public $controller;
public function __construct($model, $view, $controller) {
$this->model = $model;
$this->view = $view;
$this->controller = $controller;
}
}
This is a very powerful router because it lets us substitute individual components very easily. Imagine I wanted to use the same Model and Controller but display the result as a PDF, or RSS feed, I can just add a new view and alter the routing table, while reusing most of the original code:
$this->table['pdfexampleroute'] = new Route('Model', 'PdfView', 'Controller');
The view has been given a route so that it can be reused with different controllers. It can now link back to any route. Essentially this lets it understand the current route so that it can link back to the correct controller, it is reusable as part of many different routes. If the URL in the view was hardcoded, this would not be possible.
class View {
private $model;
private $route;
public function __construct($route, Model $model) {
$this->route = $route;
$this->model = $model;
}
public function output() {
return '<a href="mvc2.php?route=' . $this->route . '&action=textclicked">' . $this->model->text . '</a>';
}
}
This way, the view can use any controller. The controller is not passed in to the view because routes and controllers may not always be the same thing.
Finally, some code to initiate the front controller:
$frontController = new FrontController(new Router, $_GET['route'], isset($_GET['action']) ? $_GET['action'] : null);
echo $frontController->output();
Click here to see the updated script in action or click here to see the updated source code.
Compare the amount and complexity of code needed for a MVC triad in this example to your favourite "MVC" framework and you'll be surprised how much the popular frameworks force into the controller. In a real MVC implementation the controller should be doing very little.
Conclusion
On the web, there is necessity to work around the limitations of the underlying technology rather than adhering to the MVC pattern in its strictest sense. This causes knock on effects while applying the pattern as it was originally designed.
In this example, I have tried to stick to the original MVC pattern as closely as possible. Where I have deviated from it I have explained the reasons why. As usual I have tried to explain the reasoning why certain design decisions are taken, whether that's due to the underlying architecture or beause of an advantage over other methods. As I've stated previously, this reasoning is often overlooked while explaining programming concepts, yet I believe it's as important as providing concise and clear code examples.
This is a very simplistic approach to a front controller. In a real-world application it would be far more complex and robust. Now you're up to speed with the concept of a Router and why it's needed, continue to part 3 For a real-world example of a Router.
Continue to part 3: Using a Dependency Injection Container to simplify Routing in an MVC framework
References
Fowler, M (2002) Patterns of Enterprise Application Architecture; http://martinfowler.com/eaaCatalog/frontController.html
Sun Microsystems (2002) Core J2EE Patterns - Front Controller (http://java.sun.com/blueprints/corej2eepatterns/Patterns/FrontController.html)