Anders Tornblad, web developer

I'm all about the web

Reinventing a PHP MVC framework, part 2

Let's make the wheel more round

This is the second part of a series of articles about the mt-mvc PHP MVC framework. If you haven't read the first part, here it is: Reinventing a PHP MVC framework, part 1

Return to sender

In the old days, before fire was invented, responding to a request was done by calling Response.Write and writing directly to the response stream. In an MVC world (in whatever language, but especially in an object-oriented one), doing this from within a controller is a big no-no. Writing to the stream, using Response.Write or echo will only happen in the View!

In ASP.NET MVC, responding to an HTTP request is done by returning an instance of a class derived from the abstract ActionResult class. For a normal page view, you return a ViewResult object. For an AJAX request expecting JSON data, you return a JsonResult object. Some other examples are the RedirectResult, HttpStatusCodeResult, AtomFeedActionResult, and FileContentResult classes.

Most of those classes reference some model object, and will eventually render something view-like using the properties of the model object. The rendering itself, including sending HTTP headers, takes place in an implementation of the abstract ExecuteResult method. For now, I will focus only on serving ordinary views, like ASP.NET MVC does through the ViewResult class.

Some assembly needed

Using the Routing class from the previous part, we can find the names of a controller class and the method to call. We will now expect that method to return an object that has an executeResult method (first letter is lower-case, because PHP). I actually want to make my MVC framework act more in line with the MVC pattern than ASP.NET.

First of all, I don't want the controller to be able to access response artefacts like HTTP response headers, and the response content stream, because those are definitely presentation details. To have a clear separation of duties, those things should only be available to the View. Because of this, the executeResult method needs to be provided with some mechanism for setting HTTP headers and writing content. This "response wrapper" is easily mocked, for now. For testability, we also need to mock the filesystem.

This first iteration of ViewResult should set the Content-Type to text/html and then perform a standard PHP include on a view php file, using a (mocked) filesystem wrapper.

class ViewResultTests { public function ExecuteResultSetsCorrectContentType() { $controllerName = 'Home'; $viewName = 'Index'; $model = null; $viewResult = new ViewResult($controllerName, $viewName, $model); Expect($response)->toCall('setHeader')->withArguments(['Content-Type', 'text/html; charset=utf-8']); Expect($viewRootDir)->toCall('phpInclude')->withArguments(['home/index.php']); $viewResult->executeResult($response, $viewRootDir); $response->checkAll(); $viewRootDir->checkAll(); } } class ViewResult { private $controllerName; private $viewName; public function __construct($controllerName, $viewName, $model) { $this->controllerName = $controllerName; $this->viewName = $viewName; } public function executeResult($response, $viewRootDir) { $response->setHeader('Content-Type', 'text/html; charset=utf-8'); $viewRootDir->phpInclude(mb_strtolower($this->controllerName) . '/' . mb_strtolower($this->viewName) . '.php'); } }

The constructor for the ViewResult class needs the name of the controller, not the controller class. For this, we need to add a few more lines to the RoutingTests and Routing classes. That code is trivial and out of scope for this article, but you can look at it in the GitHub release.

All parts

Reinventing a PHP MVC framework, part 1
Reinventing a PHP MVC framework, part 2 (this part)
Reinventing a PHP MVC framework, part 3
Reinventing a PHP MVC framework, part 4

You'll find the code from this article in the related release on GitHub. The latest version is always available in the GitHub repository.

Add a comment