Creating a PHP website using MVC – 1

This is the first part of php mvc website design tutorial series. You already know what is MVC architecture and you might even know how to implement it. There is already a debate about the design of an mvc architecture. I am not going into that. The truth is whatever you think is easy, follow it. If there is Model, Controller and Views in your php application and have a consistent pattern around all things, you can suppose it to be in an MVC pattern.

There are numerous mvc tutorials available in internet. I found a good one sometime ago which is easy to follow up. I played with it and learned some additional things also. You can visit the link here, http://anantgarg.com/2009/03/13/write-your-own-php-mvc-framework-part-1/. Some parts of my tutorial will be similar to those. Other things that influenced me are wordpress, projectpier and other tutorials etc.

Now about  the framework, what we are creating here is a website which have an admin area and a public site area. A basic idea of a model-view-controller is we ask for a url to the server. All our calls will go through a single php page which creates the respective controller class instance and process our action. In the process it loads required Models and views. Consider, view as a template and the controller writes the data into the view and returns the html to the user. A picture is worth thousand words.mvc1

An example of a call will be www.oursite.com/car/show?name=ferrari

Yes, but where is admin area?

For that, we need another thing in the url – ‘admin’ and the url looks like this:
The processing of the framework is like this:
mvcflow
We need to check if the admin keyword is present or not in the url. If yes, check the controllers inside the admin section and if not, check the controllers inside the site section.

The folder structure for our website looks like this.

root folder
 admin/
  controller/
  view/
 common/
  model/
lib/
public/
 admin/
  css/
  js/
  images/
 site/
  css/
  js/
  images/
 index.php
site/
 controller/
 view/
system/
 config/
 log/
The first step is to create .htaccess and implement Apache mod_rewrite in site root. This will route all calls to our domain to the ‘public’ folder. Inside the ‘public’ folder we put another .htaccess file which checks the url and if it is a file or directory or a link, it won’t do any processing. So if we try to access an image/css/js file inside the public directory, we will get the correct one. All other urls are routed to the index.php file. Inside the php file we include a bootstrap file which is the index.php file in the root folder.
.htaccess file in root folder
<ifmodule mod_rewrite.c="">
 RewriteEngine on
 RewriteRule    ^$    public/    [L]
 RewriteRule    (.*) public/$1    [L]
</ifmodule>
.htaccess file in public folder
<ifmodule mod_rewrite.c="">
 RewriteEngine On

 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteCond %{REQUEST_FILENAME} !-l

 RewriteRule ^(.*)$ index.php?url=$1 [QSA,PT,L]

</ifmodule>

The index.php file in the public folder just redirects to the root folder.

<?php
@$url = $_GET['url'];
require_once ('../index.php');
?>

Most of the code in the index.php in the root folder is same as that of the one in the website given earlier though I have added some things like auto root url detection, error logging to an external file, an output trace function etc. Let’s take a look at the code.

<?php
define('ROOT',dirname(realpath(__FILE__))."/");
/**
 * 
 * DO NOT DEFINE ANY CONSTANTS HERE. DEFINE THOSE IN CONFIG.PHP
 * 
 */
$thisDir=explode("/", ROOT);
$conflen=strlen(array_pop($thisDir));
$B=substr(__FILE__, 0, strrpos(__FILE__, '/'));
$A=substr($_SERVER['DOCUMENT_ROOT'], strrpos($_SERVER['DOCUMENT_ROOT'], $_SERVER['PHP_SELF']));
$C=substr($B, strlen($A));
$posconf=strlen($C) - $conflen;
$D=substr($C, 0, $posconf);
$host='http://' . $_SERVER['SERVER_NAME'] . '/' . $D;

define('ROOT_URL', $host);

include(ROOT . 'system/config/config.php');
include(ROOT . 'lib/functions.php');

/**
 * Set error reporting
 */
function setErrorLogging(){
    if(DEVELOPMENT_ENVIRONMENT == true){
        error_reporting(E_ALL);
        ini_set('display_errors', "1");
    }else{
        error_reporting(E_ALL);
        ini_set('display_errors', "0");
    }
    ini_set('log_errors', "1");
    ini_set('error_log',ROOT . 'system/log/error_log.php');
}
/**
 * Trace function which outputs variables to system/log/output.php file
 */
function trace($var,$append=false){
    $oldString="<?php\ndie();/*";
    if($append){
        $oldString=file_get_contents(ROOT . 'system/log/output.php') . "/*";
    }
    file_put_contents(ROOT . 'system/log/output.php', $oldString . "\n---\n" . print_r($var, true) . "\n*/");
}

/** Check for Magic Quotes and remove them **/
function stripSlashesDeep($value) {
    $value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value);
    return $value;
}

function removeMagicQuotes() {
    if ( get_magic_quotes_gpc() ) {
        $_GET    = stripSlashesDeep($_GET   );
        $_POST   = stripSlashesDeep($_POST  );
        $_COOKIE = stripSlashesDeep($_COOKIE);
    }
}

/** Check register globals and remove them **/
function unregisterGlobals() {
    if (ini_get('register_globals')) {
        $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
        foreach ($array as $value) {
            foreach ($GLOBALS[$value] as $key => $var) {
                if ($var === $GLOBALS[$key]) {
                    unset($GLOBALS[$key]);
                }
            }
        }
    }
}

/** Main Call Function **/
function callHook() {
    global $url;
    global $area;
    $url = rtrim($url,"/");
    $urlArray = array();
    $urlArray = explode("/",$url);
    $controller = DEFAULT_CONROLLER;
    $action = DEFAULT_ACTION;
    //Check if the call is to admin area
    if($urlArray[0] == "admin"){
        $area = "admin";
        array_shift($urlArray);
    }
    //Controller
    if(isset($urlArray[0]) && !empty($urlArray[0])){
        $controller = array_shift($urlArray);
    }
    //Action
    if(isset($urlArray[0]) && !empty($urlArray[0])){
        $action = array_shift($urlArray);
    }
    
    
    
    //LOAD THE CONTROLLER
    $controllerName = $controller;
    $controller = ucwords($controller);
    $model = rtrim($controller, 's');
    $controller .= 'Controller';
    $dispatch = new $controller();
    
    echo $area . "-->".$controller."--->".$action;
    
    if ((int)method_exists($controller, $action)) {
        call_user_func(array($dispatch,$action));
    } else {
        error_log("Unknown page/action, Controller = ".$controller.", action = ".$action);
    }
}

function __autoload($className){
    $paths = array(
        ROOT."/lib/",
        ROOT."/site/controller/",
        ROOT."/admin/controller/",
        ROOT."/common/"
    );
    foreach($paths as $path){
        if(file_exists($path.$className.".class.php")){
            require_once($path.$className.".class.php");
            break;
        }
    }
}

$area = "site";

setErrorLogging();
removeMagicQuotes();
unregisterGlobals();
callHook();

?>

In the first block of code, we define the ROOT folder and the ROOT_URL. These two are  defined as global constants and available inside our php website everywhere. We have included two files – config.php and functions.php.  We will create these two files after exploring this file, but I’ll let you know that config.php has three constants defined – DEVELOPMENT_ENVIRONMENT, DEFAULT_CONTROLLER and DEFAULT_ACTION and the values are ‘true’, ‘index’, and ‘index’ respectively.

Also Read:   Creating a PHP website using MVC – 4 Creating Model class

From now on, whatever constants we may need, define those in the config.php only.

The setErrorLogging() function checks whether the current session is a development environment or not by checking the constant DEVELOPMENT_ENVIRONMENT. During the time of development, set this to true and after publishing your site without any errors, set this to false. If the current session is a development session, it configures php to display all errors in the page and if not, it doesn’t show any error or warning on our website. In both occasions, if any error is happened, it will be written to the error_log.php so that we can open this file later and check the error details.

The trace function accepts any value and writes it to output.php file. This is a php file and a die() method is added on first line, so we cannot see the data if it is opened in the browser. We need to open it in the editor to see the data. There is an optional parameter $append, if true, it will append the data to the old data, else, it will erase all before writing the new data.

The next two functions stripSlashesDeep and removeMagicQuotes checks the input and sanitize the data. You also know what unregisterGlobals() do.

Also Read:   How to automate Godot Android build process in 5 easy steps

The callHook() is the main processing function which checks and determines the controller, load and call it. First it checks the url, strips out any leading slashes and split the url by ‘/’. The output will be an array. If the first element is ‘admin’ this is an admin area call. The second element will be the controller and the third, controller action to be performed. If the first element is not ‘admin’, it is a site area call and that will be the controller and the next element is the action. It creates an instance of the controller and calls the action on it. If a controller does not exist, it redirects to an error page.

The __autoload function is called before a class is called so that we can check and load the corresponding php file for that class. This is helpful because we don’t want to load all the php files on startup. It loads whatever is required whenever is needed.

The above things are all methods. First we default the $area to the site area and calls all the methods.

Now the config.php

<?php
define('DEVELOPMENT_ENVIRONMENT', TRUE);
define('DEFAULT_CONROLLER', 'index');
define('DEFAULT_ACTION', 'index');
?>

functions.php is an empty file now. We will add things into it later when needed.

Also Read:   Creating a PHP website using MVC – 2 Creating the Controller and Template

Comment out the controller loading section and test it in the browser. You can see the area, controller and the action is parsed perfectly from the url.

Call: http://localhost/mvc_blueprint/
site-->index--->index

Call: http://localhost/mvc_blueprint/car/get
site-->car--->get
 
Call: http://localhost/mvc_blueprint/admin/main/login
admin-->main--->login

In the next part, we will create a base controller from where all our custom controllers are extended, a base Model class and a template renderer.

[Total: 0    Average: 0/5]