Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / PHP

PHP MVC with .NET like controller

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
28 Mar 2013CPOL4 min read 49.2K   1.3K   25   12
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)


Written By
Software Developer (Senior)
Netherlands Netherlands
I'm a developer with 22+ years of experience. Starting of on a MVS mainframe, moving to building big multi-tier ERP systems with unix backends, to building web-based BI-Portals. I've seen a lot of different languages, environments and paradigmes.

At this point my main interest is webdevelopment. Mainly javascript/typescript and ASP.NET. But I also like getting my hands dirty on some PHP.

My main focus has been shifting towards full javascript the past years. Almost anything can be accomplished now. From building full offline webapps, to IoT (Tessel), to server (node).

Comments and Discussions

 
Questionerror with call_user_func Pin
Member 1006851921-May-13 20:05
Member 1006851921-May-13 20:05 
AnswerRe: error with call_user_func Pin
Sebastiaan Meijerink29-May-13 5:15
professionalSebastiaan Meijerink29-May-13 5:15 
Did you alter anything or did you just instal it?
Last time I ran this it worked.
AnswerRe: error with call_user_func Pin
Member 1041253225-Dec-14 12:03
Member 1041253225-Dec-14 12:03 
SuggestionHave a look at the Scavix WebFramework Pin
DaSpors23-Apr-13 20:58
DaSpors23-Apr-13 20:58 
QuestionMy vote of 5 Pin
m.moosa6-Apr-13 0:21
m.moosa6-Apr-13 0:21 
GeneralMy vote of 5 Pin
fjmy0723-Jan-13 21:15
fjmy0723-Jan-13 21:15 
QuestionIs it same for CakePHP and Kohana? Pin
Zamshed Farhan15-Jan-13 19:06
Zamshed Farhan15-Jan-13 19:06 
AnswerRe: Is it same for CakePHP and Kohana? Pin
Sebastiaan Meijerink15-Jan-13 20:53
professionalSebastiaan Meijerink15-Jan-13 20:53 
GeneralRe: Is it same for CakePHP and Kohana? Pin
Zamshed Farhan15-Jan-13 20:56
Zamshed Farhan15-Jan-13 20:56 
GeneralRe: Is it same for CakePHP and Kohana? Pin
Sebastiaan Meijerink15-Jan-13 21:10
professionalSebastiaan Meijerink15-Jan-13 21:10 
GeneralRe: Is it same for CakePHP and Kohana? Pin
Zamshed Farhan15-Jan-13 21:22
Zamshed Farhan15-Jan-13 21:22 
GeneralMy vote of 5 Pin
briciocardoso31-Oct-12 4:36
briciocardoso31-Oct-12 4:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.