Anders Tornblad, web developer

I'm all about the web

Reinventing a PHP MVC framework, part 1

Let's reinvent the wheel

This is the first part of a series of articles about the mt-mvc PHP MVC framework.

I wanted to know how ASP.NET MVC does what it does, so I decided to find out... by trying to reinvent it... in PHP. My line of thought was this:

  • I know how to USE the ASP.NET MVC framework
  • I know the effects of using the various features of the ASP.NET MVC framework
  • I know the principles of TDD
  • I should be able to reinvent (or reverse-engineer) a working MVC framework by adding unit tests for increasingly complex use of MVC, and making one or a few tests pass at a time
  • I also want to become a better PHP developer

I am perfectly aware of the fact that there are lots of MVC frameworks for PHP that are really capable of taking care of business, but this is not a website development effort. This is a learning effort. Reinventing the wheel works fine for learning - not for production code.

MVC the ASP.NET way

Let's start with something simple. The most basic use of ASP.NET MVC, in the default setting, appears to work by separating the request path of an incoming request into a Controller class name, a View method name, and an optional parameter value that gets passed into the method. Also, there are default values for all parts of the path.

First set of tests

I imagine a class that's solely responsible for parsing a path, and suggesting the name of a controller class, and a method to call, so I write some tests for that class first. Hooking things up to the PHP HTTP infrastructure gets added later.

class RoutingTests { public function CheckAllDefaults() { $routing = new Routing(); $route = $routing->handle(''); The($route->controllerClassName)->shouldEqual('HomeController'); The($route->methodName)->shouldEqual('Index'); The($route->parameter)->shouldNotBeSet(); } public function CheckDefaultMethodNameAndParameter() { $routing = new Routing(); $route = $routing->handle('Articles'); The($route->controllerClassName)->shouldEqual('ArticlesController'); The($route->methodName)->shouldEqual('Index'); The($route->parameter)->shouldNotBeSet(); } public function CheckDefaultParameter() { $routing = new Routing(); $route = $routing->handle('Categories/List'); The($route->controllerClassName)->shouldEqual('CategoriesController'); The($route->methodName)->shouldEqual('List'); The($route->parameter)->shouldNotBeSet(); } public function CheckNoDefaults() { $routing = new Routing(); $route = $routing->handle('Products/Item/123x'); The($route->controllerClassName)->shouldEqual('ProductsController'); The($route->methodName)->shouldEqual('Item'); The($route->parameter)->shouldEqual('123x'); } }

These tests are about the default out-of-the-box behavior of the routing subsystem. More advanced features, like registering custom url patterns, get added later.

class Routing { public function handle($url) { $parts = explode('/', $url); $controllerName = @$parts[0]; $methodName = @$parts[1]; $parameter = @$parts[2]; if (!$controllerName) $controllerName = 'Home'; if (!$methodName) $methodName = 'Index'; return (object) [ 'controllerClassName' => $controllerName . 'Controller', 'methodName' => $methodName, 'parameter' => $parameter ]; } }

Usefulness right now

This class does the bare minimum, and making some real use of it requires a lot of nuts and bolts in place – some URL redirection, a request/response pipeline system, some use of reflection to dynamically create controller instances and calling methods, a lot of thought about how to connecting views to the controller methods, and so on. Don't worry; all of that will be covered in the following posts.

All parts

Reinventing a PHP MVC framework, part 1 (this part)
Reinventing a PHP MVC framework, part 2
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