Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

PHP MVC with .NET like controller

5.00/5 (14 votes)
28 Mar 2013CPOL4 min read 51.6K   1.4K  
A PHP MVC modal that mimics C# like Controllers.

Sample Image

Introduction

I primarily work with ASP.NET and therefore fell in love with the simplicity of their MVC model. You just click new controller, yes please create the view, and you are presented with a piece of controller code that couldn't be more simplistic.

But the internet is mostly build out of PHP/MySQL and when you're building a site for the bakery at the corner, including Zend or some other large MVC, might be a bit over the top. I wanted to create a simple MVC modal in PHP, where the controller class could be just a simple as the .NET variant.

In .NET you can chose several template engines. In the modal I present you could easily plug-in any template engine you'd like. I went for Smarty. http://www.smarty.net/

I am in now way claiming this to be unique, there are a lot of great frameworks out there that do exactly the same. Like CodeIgniter. But I thought it would be fun to see how you could roll your own.

The code

First things first. Because we would like a MVC modal, we need al the request to be routed through a single source. This is done by changing the .htaccess file.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(\.css|\.js|\.jpg|\.jpeg|\.png|\.gif|\.flv|\.swf)$
RewriteRule ^.*$ index.php

Note the exception list. You wouldn't want to route CSS through a controller. Although that might be the place where you could transform your .sass or .less files in to .css. The same goes for minified JavaScript files or optimised images.

Bootstrapping

I've set up an ini file. That contains some valuable settings. The first thing index.php should do is parse this file.

[settings]
controller_path="controllers/"
view_path="views/"
baseURL="/subdir"

baseUrl could be empty. If this was installed at the root of the site

index.php is then going to include smarty and do some basic stuff. And then do the most important part of parsing the incoming request URL.

If the URL is home, then the homeController.php should contain that controller. So if the URL is products/details/10, productsController.php should be loaded.

PHP
<?php
    
require_once('libs/Smarty.class.php');    

$config = parse_ini_file("config.ini", TRUE); 

if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD'])) {
    //This will hold the request data. If there is any.
    $requestData = '';
    //The method
    $method = $_SERVER['REQUEST_METHOD'];

    if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
        //There is some data in the request. We would like to have this.
        $httpContent = fopen('php://input', 'r');
        while ($data = fread($httpContent, 1024)) {
            $requestData .= $data;
        }
        fclose($httpContent);
    }    
    
    //If we are installed in a subdirectory, we'd like to analyse the part after this directory
    $urlString = substr($_SERVER['REQUEST_URI'], strlen($config['settings']['baseURL']));
    //Split the url in pieces
    $urlParts = explode('/', $urlString); 

    if (isset($urlParts[0]) && $urlParts[0] == '') {
    	array_shift($urlParts);
    }
	
    if (!isset($urlParts[0]) || $urlParts[0] == '') {
        //If the url does not contain more then / we're going to start the default controller. Which is home
        $mainViewPath="home/";
        $currentController = "HomeController";
        $controllerFile = "homeController.php";	    		
    	}
    else	{
        //There is a directive after the / so that is going to be our controller
        $mainViewPath=strtolower($urlParts[0]);
        $currentController = ucfirst($urlParts[0]) . "Controller";
        $controllerFile = $urlParts[0] . "Controller.php";	    			
        //This will make the 'action' part of the url the first directive
        array_shift($urlParts);
    	}    
    
}
else    {
    header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad request");      
}
?>

Abstract class Controller

So we now know which controller class we would like to start. If someone gave us the request URL/products, we'd want to load productsContoller.php and create an instance of the ProductsController.

But first we need to focus on creating an abstract class Controller. In order to get our controllers to have such a simple structure, we'll need a super class that will do al the heavy lifting.

All controllers will inherit from this class. The Controller class needs to do a few things. It needs to look at what is left of the request URL and determine which action within the controller should be started. So if we get /products, there is no action defined and we'll start the index action. If we get /products/details we wil start the details action.

The second thing the Controller class needs to do is create the viewBag. This is a 'bag' of data that the implementing controller can add data to. But next to that I like to also assume that POST data is JSON and that all POST data is added to the viewBag before starting the controller action. This will allow setting template variables from JavaScript. (But that might be better for a next time).

The third thing he does is determine the view.html file.

The last thing the Controller class does is implement the View() function, which applies the viewBag to the template.

The constructor

PHP
//@contructor
//@param controllerName, The name of the controller. E.g. ME
//@param urlParts The urlParts that are left.
//    If the original request was products,details,10 then this wil hold details,10
//@param data de JSON data, The request data.
public function __construct($controllerName, $urlParts, $data)
{
    $this->fullActionPath=implode("/", $urlParts);
    
    $this->method=$_SERVER['REQUEST_METHOD'];
    
    //We are assuming the data to be a JSON string. In a final version this should have some error handling
    if($data=='')   {
        $data='{}';
    }
    $this->viewBag=json_decode($data);
    
    //The action is the first part	
    $action=$urlParts[0];
    
    if (count($urlParts) > 1 && $urlParts[1] != '') {
        //Now we need to find the identifiers
        array_shift($urlParts);
        foreach ($urlParts as $uid) {
            if ($uid != '') {
                $this->uid[] = $uid;
            }
        }
    }			
    if(!isset($action) || $action=='')	{
        //If there is no action, we'll start the default action.
        $action="index";
    }
    
    //The view html
    $this->viewHtml=strtolower($action) . ".html";
    
    try	{
        //call_user_func gives a fatal error, which we cannot catch.
        //So we cannot handle asking for a unknown action
        //This is why we'll use the ReflectionClass's getMethod which will throw an exception
        $reflector = new ReflectionClass($this);
        $method=$reflector->getMethod($action);
        //If all works. We'll start the action.
        call_user_func($controllerName . "::" . $action, $this->uid);
    }
    catch(Exception $exc)	{
        //If the view doesn't exists, we'll start the default view.
        //In a final version this should start the ViewUnknown action.
        call_user_func($controllerName . "::index");
    }
}

View function

PHP
//@method View 
//@description Combines the view with the viewBag data. 
public function View()
{
    //Detertime the full path to the view file
    $viewPath=$GLOBALS['config']['settings']['view_path'] . $this->viewHtml;
    
    if	(file_exists($viewPath))	{
        //If the file exists use the smart engine
        //This would be the place to use some other engine like Mustache
        $this->smarty = new Smarty();
        
        foreach($this->viewBag as $key => $value) {
        	$this->smarty->assign($key, $value);
        }
        
        $this->smarty->display($viewPath);
    }
    else	{
        header('HTTP/1.0 404 Not Found');
        echo "404 Not Found";
        echo "The page that you have requested could not be found.";					
    }
}

We'll need to add require_once('classes/Controller.php') to our index.php.

Home view and controller

Now we're ready to create views and controllers, but first I'll remind you of the .ini file which has controller_path and view_path. This determines the file structure below:

  • baseURL
    • classes
      • Controller.php
      • Smarty.class.php
    • controllers
      • homeController.php
    • views
      • home
        • index.html
    • .htaccess
    • config.ini
    • index.php

HomeController

PHP
<?php
/*
    @class HomeControlelr
    @extends Controller
    @description Dit is de controller for home
*/
class HomeController extends Controller
{

    //@method index De index action
    public function index()	
    {
        //Lets set some variable to test the template
        $this->viewBag->hellomessage="Hello world!";
        return $this->View();
    }
}
?>

home/index.html

HTML
<html>
<head>
    <title>Index</title>
</head>
<body>
    <h1>{$hellomessage}</h1>
</body>
</html>

That is basically it.

Conclusion

Though this is going to need al lot more detail to be used in a real live situation I believe it shows you can create a simple MVC with PHP. For small sites this should almost be enough. In the zip files I've worked the example out to some more detail. It contains a worked out example of the bakery webshop. It also works with a shared template, so that we can wrap html around our views. One of the most important parts of MVC is missing though. The modal. I'll get to that in the next article.

This being my first article and me not really being a core PHP coder, any comments will be welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)