In a previous article, we decided on developing our own simple PHP Framework, on which we would then base our application. When working with a Framework, or in our case creating a framework, we need to decide on how we are planning on structuring our application. All of the current frameworks for PHP have a set structure that defines how components and layers of the application communicate with each other. Most of these frameworks utilise an MVC approach, or Model-View-Controller. The Model-View-Controller approach helps us to separate our data, from our application, from our pages or views. MVC is a concept that was developed back in the 1970’s, and as a result does not perfectly fit with the current web development trends of today. In reality, most frameworks, and ours included follow more of a MVP, or Model-View-Presenter design pattern, but we aren’t here to debate the difference! In the traditional MVC approach, the view gets its data directly from the model. In our approach, our controller will handle all of the interaction between our View and our Model.

What is MVC?

MVC is a method, or a design pattern that separates components of our application. We can separate all of our class objects into 3 categories:

  • Model – The model represents all of our application data, and application specific components and services.
  • Controller – Our controllers are responsible for interacting with our Model and our Views, in order to manage what the user can see and do with our application.
  • View – Our views are going to interact with our controllers in order to present our application data to our user, and capture user input, which will be sent back to the controller.

In order to implement our MVC design, we will need to structure our application in such a way that it is clear which are the models, views and controllers.

Our File System Structure

To facilitate our MVC approach, we will utilise a front controller, which in simple terms is a file that will receive and handle all requests for our application. In our case, we will call the file index.php, and we will redirect all application traffic to this file. This file will sit in a directory named “app” that we will use to structure our files.

Within the “app” directory, we will have sub directories named:

  • core – this directory will hold core files and services for our application
  • models – this will hold all of our model files
  • views – this will hold all of our view files
  • controllers – this will hold all of our controllers

This should give us the structure below:

We now need to configure our application to route all requests to our index.php file. On a typical server setup you would use .htaccess rewrite rules to redirect traffic. In our case, on Google App Engine, we need to configure this using our app.yaml file. We looked at this briefly when we set up our development environment. We will now modify the file to contain the following:

application: buzi-log
version: dev-001
runtime: php55
api_version: 1

builtins:
- remote_api: on

handlers:
- url: /public
  static_dir: public
  
- url: /.*
  script: app/index.php

You will see we have created 2 URL handlers, the first one routes all requests to the /public folder to a static directory. We can use this directory to hold any css, images, javascript etc. The second, /.* catches all requests that have not yet matched a URL Handler, and redirects them to our app/index.php file. Now, any requests to the /public directory will return static resources, and any other requests will be routed to index.php.

Next, we need a way to autoload our required classes, which will include all of our models, views and controllers. In our Test Based Development article, we used composer’s autoload feature. We can do the same here. We just need to update our classmap declaration in composer.json to include the directories that will hold our classes.

 

"autoload": {
	"classmap": [
	   "app/controllers",
	   "app/core",
	   "app/views",
	   "app/models"
	]
}

To update the classmap with composer, you will also need to run the following terminal command:

php composer.phar dump

Let’s now have a look at how to handle requests using our index.php file. The first thing we need to do, is include composer’s autoload file to ensure our classes are autoloaded.

<?PHP //index.php
	require_once '../vendor/autoload.php';
?>

Our index.php file, which handles all requests for our application, will now include our composer autoload script, and handle class loading for us automatically. All we have to do is ensure each time we add a new class, we also update our autoload script by calling composer’s dump function.

Now that we are able to autoload our classes, let’s begin to build a class that will handle our routing and later, other important features of our application. This will be a core file, located in our core directory, and we will create a class named App. As a general rule, I name my class files, classname.class.php, in this case app.class.php. This is not required, app.php will work fine as well. To begin, let’s define a set of tests for our App class.

<?PHP // tests/AppTest.php

class AppTest extends PHPUnit_Framework_TestCase{
	
	public $TestApp;
	
	public function setUp(){
		$this->TestApp = new App('/testcontroller/testmethod/testparam/');
	}
	
	public function testAppHasDefaultControllerSet(){
		$this->assertAttributeNotEmpty('Controller', $this->TestApp, 'The default controller has not been set');
	}
	
	public function testAppHasDefaultMethodSet(){
		$this->assertAttributeNotEmpty('Method', $this->TestApp, 'The default method has not been set');
	}
	
	public function testAppHasDefaultParamArraySet(){
		$this->assertAttributeInternalType('array', 'Params', $this->TestApp, 'The default params array has not been set');
	}
	
	public function testAppParsesUrlCorrectly(){
		$ExpectedResult[] = 'testcontroller';
		$ExpectedResult[] = 'testmethod';
		$ExpectedResult[] = 'testparam';
		$this->assertEquals($ExpectedResult, $this->TestApp->URL, 'The expected result does not match the actual result');
	}
}
	
?>

To begin, we have created an instance of our App class, in PHPUnit’s setUp() method. I have passed in a simple test request, ‘/testcontroller/testmethod/testparam’, ideally when this request is parsed, we would like an array to come back with 3 items, testcontroller, testmethod, testparam.

The first test we have created is to ensure that a default controller has been set, incase a valid controller name is not passed to our App instance. The second is to ensure that a default method has been set, and a third test ensures that there is a default array of parameters available. At this stage we do not care what the defaults are, as long as they have been set.

The next test will make sure our App class processes the request into the useful controller, method and parameter components we are looking for. To do this, we set the array that we expect to receive back, in $ExpectedResult. We then assert that our expected array equals the stored, parsed URL in our App instance. By setting up these tests first, we can ensure that our code is working as expected, as we develop it. Let’s now have a look at the App class, and see how we have attempted to satisfy the requirements of our test case.

<?PHP // core/app.class.php

class App{
	
	protected $Controller = 'Dashboard';
	protected $Method = 'index';
	protected $Params = [];
	
	public $URL;
	
	public function __construct($Request){
		$this->URL = $this->parseUrl($Request);
	}
	
	protected function parseUrl($Request){
		if(isset($Request)){
			$URL = rtrim($Request, '/');
			$URL = ltrim($URL, '/');
			$URL = filter_var($URL, FILTER_SANITIZE_URL);
			$URL = explode('/', $URL);
			return $URL;
		}
	}

}	
	
?>

To start our App class, we have defined some default values. We have set a default controller to ‘Dashboard’, which we will use to load a user’s dashboard if no other valid request is received. We have also set a default method to call, in this case we have named in index. This will be called if no valid method is requested. We can use this to define default behaviour, should we not receive a valid request. We have also set an empty array of parameters in order to set up our methods to be able to receive any parameters that may or may not be passed in.

You will then see we have started defining a _construct() method, which will be called when our App class is instantiated. At this stage we have just one line, a call to a method named parseUrl(). This will be responsible for processing the request URL, and give us the useful parts, such as the requested controller, controller method, and any parameters.

The parseUrl method first performs an rtrim on the request parameter, this is to remove any unwanted ‘/’ characters from the end of our request string. for example, in our test we passed in ‘/testcontroller/testmethod/testparam/’, after the rtrim, it should now read ‘/testcontroller/testmethod/testparam’. The next step simply performs an ltrim, removing any ‘/’ from the start of the string. We should now have ‘testcontroller/testmethod/testparam’. We then want to ensure the request string we have been provided is valid, which can be done using filter_var’s FILTER_SANITIZE_URL filter. We then explode our string, at each point the ‘/’ character is present. This will give us un array of all of the components of our URL.

We should have now satisfied all of the tests we set for the class so far. Let’s run composer’s dump function to update our autoload file, and run our tests using phpunit.

It works! Keen observers will note however that our parseUrl method is fairly verbose. It could really be called with just one line. The advantage of unit testing with PHPUnit, is we can refactor this code, and test it very quickly. Let’s refactor and retest it now.

<?PHP // core/app.class.php

class App{
	
	protected $Controller = 'Dashboard';
	protected $Method = 'index';
	protected $Params = [];
	
	public $URL;
	
	public function __construct($Request){
		$this->URL = $this->parseUrl($Request);
	}
	
	protected function parseUrl($Request){
		if(isset($Request)){
			return $URL = explode('/',filter_var(ltrim(rtrim($Request, '/'), '/'), FILTER_SANITIZE_URL));
		}
	}

}	
	
?>

We have quickly and easily refactored our code, and tested it against our existing tests! This is one of the great advantages to test based development. We can modify our source code, and test against our existing tests to ensure we have not broken any other functionality.

The task, is to create an instance of our App class in our index.php file, as this will handle all of our requests.

<?PHP // app/index.php
	
require_once '../vendor/autoload.php';

$App = new App($_SERVER['REQUEST_URI']);
	
?>

By passing the server’s REQUEST_URI index, we have passed to our App class the requested URI. PHP.net’s definition of ‘REQUEST_URI’ is, ‘The URI which was given in order to access this page; for instance, /index.html’. This is exactly what we need to determine the requested controller, method and parameters. We now need to use the information we have extracted from this URI, to determine, and set which controller and method will be used by each request.

Lets define some tests to determine if we have satisfied our requirements:

  1. The App class creates an instance of the correct controller
  2. The App class sets the correct method to be called

Let’s see how these will translate to our test class.

	public function testAppCreatesInstanceOfCorrectController(){
		$TestApp = new App('/testonlycontroller/testonlymethod/testonlyparameter');
		$this->assertAttributeInstanceOf('testonlycontroller', 'Controller', $TestApp, 'The incorrect controller has been created');
	}
	
	public function testAppCreatesInstanceOfDefaultController(){
		$TestApp = new App('thiscontrollerdoesnotexist/testmethod/testparam');
		$this->assertAttributeInstanceOf('Dashboard', 'Controller', $TestApp, 'The default controller has not been created');
	}
	
	
	public function testAppSetsCorrectMethod(){
		$TestApp = new App('/testonlycontroller/testonlymethod/testonlyparameter');
		$this->assertEquals('testonlymethod', $TestApp->getMethod(), 'The incorrect method has been set');
	}
	
	public function testAppSetsDefaultMethod(){
		$TestApp = new App('/testonlycontroller/thismethoddoesnotexist/testonlyparameter');
		$this->assertEquals('index', $TestApp->getMethod(), 'The default method has not been set');	
	}

Here we have created our test functions, and in total 4 tests. One tests for a known controller/method, the other sets a controller or method that does not exist. In this instance, our default should be set instead. If we ran these tests now they would fail, as we do not currently have the testonlycontroller, with the testonlymethod, nor do we have the Dashboard class with the index method. Lets quickly create these now.

<?PHP // app/controllers/testonlycontroller.class.php
	
class testonlycontroller{
	public function testonlymethod($params){
		return $params;
	}
}

?>
	
<?PHP // app/controllers/dashboard.class.php

class Dashboard{
	public function index($Params){
		return $Params;
	}
}
	
?>

Now that we have these controllers and the correct methods available, will our tests pass? Lets see.

Our tests failed, as we have not yet told our App class how to set the controllers and methods based on the URI request. Let’s implement that now.

	// core/app.class.php

	public function __construct($Request){
		$this->URL = $this->parseUrl($Request);
		
		if(isset($this->URL[0])){
			if(class_exists($this->URL[0])){
				$this->Controller = $this->URL[0];
			}
		}
		$this->Controller = new $this->Controller;
		
		if(isset($this->URL[1])){
			if(method_exists($this->Controller, $this->URL[1])){
				$this->Method = $this->URL[1];
			}
		}
		
	}

	public function getMethod(){
		return $this->Method;
	}

In our App class construct method, we have tested to see if URL[0] is set, which is the variable that should contain our controller name. If it is set, we have then tested to see if a class exists with that name. If so, we then create an instance of that class and set it to the $this->Controller attribute of our App instance.

We do much the same with our method. First, we see if URL[1] is set, then we make sure our selected controller has a method with that name, and if so, we set the $this-Method attribute.

If any of these conditions fail, we should have our default values still set.

We have also created a getMethod() method to return the name of the set method for testing purposes.

Our tests should now pass! Let’s check.

Success! Our tests have passed. Our App class is now able to receive a request from a URI, parse the URI, and set the required controller and methods to use. Our next step, is to set the parameters, and call the controllers selected method.

	// core/app.class.php

	public function __construct($Request){
		$this->URL = $this->parseUrl($Request);
		
		if(isset($this->URL[0])){
			if(class_exists($this->URL[0])){
				$this->Controller = $this->URL[0];
			}
		}
		$this->Controller = new $this->Controller;
		
		if(isset($this->URL[1])){
			if(method_exists($this->Controller, $this->URL[1])){
				$this->Method = $this->URL[1];
			}
		}
		
		$this->Params = $URL ? array_values($URL) : [];
		
		call_user_func([$this->Controller, $this->Method], $this->Params);
		
	}

The line setting the parameter array simply checks to see if any parameters have been passed to us, and if so, re-bases the url array so the keys start back at 0 and sets that as the parameters array, or if not set, sets an empty array.

We then use the call_user_func method to call the selected method, on the selected controller, and pass the parameters array with the request.

Our App class is now able to set the correct Controller, looking back at the bigger picture though, we also need a Model and a View set to the controller. One way we can achieve this, is to create a parent Controller class, which we can then extend for all of our custom controllers. The Controller class can then contain methods we can use to set our custom controller’s model and view classes. While we are there, we can also implement an index method that will handle the default case we have set above.

<?PHP // core/controller.class.php
	
class Controller{
	
	protected $Model;
	protected $View;
	
	public function Model($name){
		if(class_exists($name)){
			$this->Model = new $name();
		}
	}
	
	public function View($name, $data){
		if(class_exists($name)){
			$this->View = new $name($data);
		}
	}

	public function index(){
		echo get_class($this).' index method';
	}
}	

?>

We have created 3 functions within our Controller class, the Model and View methods simply check to see if a class is available with the requested name, and if so creates and instance of that class. In the View method, we also pass in an array of data, which can be used to fill our view with information.

We have also defined a default index() method, which will ensure that each controller that is created will automatically have an index method available (as this is our default, or go to method). This method can be overwritten to provide better functionality on a per-controller basis, and we can be assured that this method will be called should no custom index method be present in any controller. In this method we have simply echo’d the controller name, with index method appended, to notify us we have called the default method. As your application grows, you will want to provide some default functionality here, or a message like “The resource you have requested could not be found”.

We now need to update our controller classes, to extend from the Controller base class. I have done this below for our Dashboard class.

<?PHP // controllers/dashboard.class.php

class Dashboard extends Controller{
	
	public function index(){
		$this->Model('User');
		$this->Model->setName('Matt Presland');
		
		$this->View('DashboardView',$this->Model->getName());
		$this->View->render();
	}
	
}
	
?>

	
<?PHP // models/user.class.php

class User{

	private $Name;
	
	public function setName($Name){
		$this->Name = $Name;
	}
	
	public function getName(){
		return $this->Name;
	}
}	
	
?>
	
<?PHP // views/dashboardview.class.php

class DashboardView extends View{
	public $Data;
	
	public function __construct($Data){
		$this->Data = $Data;
	}
	
	public function render(){
		print_r($this->Data);
		return true;
	}
}
	
?>

By extending our Controller class, our Dashboard class now has access to the Model() and View() methods. I have created a simple model User class, that simply sets and retrieves a name. And a simple DashboardView class to act as a view. The view simple accepts a $Data parameter, and when render() is called prints the data to the screen.

We can now set our model in our Dashboard controller using $this->Model(‘User’); we can then interact with our model, set our view, and pass it data from our model, and ask the view to render.

We now have a working, basic Model-View-Controller setup, where we call a controller, which accesses a model, and passes data to our view for display to the end user. You will notice, our view has no knowledge of how the controller or model work. It simply presents data on behalf of the model and controller.

We do still have an issue though. What if our controllers, or our models have dependencies! We have not considered dependency injection in our simple MVC setup. If you have not read the article in this series on Dependency Injection I suggest you do so now. How can we set up our dependency injection container for this setup?

Firstly, let’s create a container class that is able to create the objects we are currently using. This will be a core file.

<?PHP // core/container.class.php
	
class Container{
	public static function newDashboard(){
		return new Dashboard();
	}	
	
	public static function newUser(){
		return new User();
	}
	
	public static function newDashboardView(){
		return new DashboardView();
	}
}

?>

Next, lets make our App class, which will be handling most of the heavy setup lifting for our application, extend from our Container class. We will then be able to create our objects and dependencies by calling App::newObject();

<?PHP

class App extends Container{
	
	protected $Controller = 'Dashboard';
	protected $Method = 'index';
	protected $Params = [];
	
	public $URL;
	
	public function __construct($Request){
		$this->URL = $this->parseUrl($Request);
		
		if(isset($this->URL[0])){
			if(class_exists($this->URL[0])){
				$this->Controller = $this->URL[0];
			}
		}
		$ControllerName = 'new'.$this->Controller;
		$this->Controller = self::$ControllerName();
		
		if(isset($this->URL[1])){
			if(method_exists($this->Controller, $this->URL[1])){
				$this->Method = $this->URL[1];
			}
		}
		
		$this->Params = $URL ? array_values($URL) : [];
		
		call_user_func([$this->Controller, $this->Method], $this->Params);
		
	}
	
	protected function parseUrl($Request){
		if(isset($Request)){
			return $URL = explode('/',filter_var(ltrim(rtrim($Request, '/'), '/'), FILTER_SANITIZE_URL));
		}
	}
	
	public function getMethod(){
		return $this->Method;
	}

}	
	
?>

All we have done here is make the App class extend the Container class. We have also modified the line that creates our Controller object, from:

$this->Controller = new $this->Controller;

To:

$ControllerName = 'new'.$this->Controller;
$this->Controller = self::$ControllerName();

To ensure the Container method that creates the selected object construction is called. As a rule, I will be naming all of the dependency builder methods in the Container class new<Object>, so for each selected controller, we simply need to add ‘new’ to the beginning of the string. We then need to update our Controller base class, and our Dashboard controller to create the models and views in the same way. To simplify this, I have created the Model and View objects in the Dashboard controller, and passed them to our Model and View method in the base Controller class to be set.

<?PHP // core/controller.class.php
	
class Controller{
	
	protected $Model;
	protected $View;

	public function Model($Model){
		$this->Model = $Model;
	}
	
	public function View($View){
		$this->View = $View;
	}

	public function index(){
		echo get_class($this).' index method';
	}
}	

?>
	
<?PHP // controllers/dashboard.class.php

class Dashboard extends Controller{
	
	public function index(){
		$this->Model(App::newUser());
		$this->Model->setName('Matt Presland');
		
		$this->View(App::newDashboardView());
		$this->View->setData($this->Model->getName());
		$this->View->render();
	}
	
}
	
?>

We now have a basic MVC setup that incorporates our dependency injection container. In the upcoming articles, we will expand on our App class, and introduce session handling, security, error and event logging, a user interface, and user handling. In the next article, we will focus on session handling.