Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Create Your Own MVC Framework

Learn how to create your own MVC framework to better understand how the framework core works.


advertisement

The Model-View-Controller (MVC) pattern is a widely used software architecture for web applications. It divides the application into three components and defines relationships between them:

  • Model represents database logic — it is used to communicate with the database
  • Controller represents application logic — PHP code
  • View represents presentation logic — HTML code

This way of organizing the application separates the design, programming and the data, allowing the programmers to reuse more code and be more productive. Also, it improves collaboration on projects where designers and programmers are working together.

For most of projects, you would use an already built PHP MVC framework. However, it is good to learn how to create your own MVC framework in order to have a better understanding of how the framework core works. It will also prepare you for large projects where creating a custom framework is more efficient than using an existing one.

Directory Structure

We will use the following directory structure in this tutorial:

  • application — contains application-specific files
    • controllers — all controllers go here
    • models — all models go here
    • views — all views are stored here
  • config — contains configuration files
  • core — core classes will be stored here
  • css — folder for CSS stylesheets
  • images — folder for images
  • js — folder for JavaScript files


Note that all folder names are lower case. Each folder has an index.html file to prevent directory index access:

<html>
<head>
	<title>403 Forbidden</title>
</head>
<body>

<p>Directory access is forbidden.</p>

</body>
</html>

URL Rewriting

MVC Frameworks route all URL requests to one file — index.php. We will do the same by creating an .htaccess file:

RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond $1 !^(images|photos|css|js|robots\.txt)
RewriteRule ^(.*)$ /index.php?url=$1 [L] 

So, all HTTP requests (except image, photos, css and js folders and robots.txt) are now handled by index.php. Index.php configures the correct file paths and runs the bootstrap.php file:

<?php
// Directory separator is set up here because separators are different on Linux and Windows operating systems
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(dirname(__FILE__)));
$url = $_GET['url'];
require_once(ROOT . DS . 'core' . DS . 'bootstrap.php'); 

Autoloading PHP Classes

One of the most important features of any framework is class autoloading. It enables you to just create a file with a new class (e.g. a controller or a model) and it becomes instantly available for use in the application. Autoloading is handled by bootstrap.php file:

<?php
// Load configuration and helper functions
require_once(ROOT . DS . 'config' . DS . 'config.php');
require_once(ROOT . DS . 'core' . DS . 'functions.php');

// Autoload classes
function __autoload($className) {
	if (file_exists(ROOT . DS . 'core' . DS . strtolower($className) . '.php')) {
        require_once(ROOT . DS . 'core' . DS . strtolower($className) . '.php');
    } 
	else if (file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php')) {
        require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php');
    }
	else if (file_exists(ROOT . DS . 'application' . DS . 'models' . DS . $className . '.php')) {
        require_once(ROOT . DS . 'application' . DS . 'models' . DS . $className . '.php');
    }
	else if (file_exists(ROOT . DS . 'core' . DS . 'sql.php')) {
        require_once(ROOT . DS . 'core' . DS . 'sql.php');
    }
}

// Route the request
Router::route($url); 

In this example, all classes inside core, controllers and models folders are automatically loaded. Naming conventions must be followed here: controller filename is always the lowercase class name, while the model filename is the same as the class name.

Bootstrap.php also loads the configuration file (config.php) and the file with helper functions (functions.php). At last, it sends the URL to the router class.

Configuration

The configuration file contains the database credential and other information that is available throughout the entire application:

<?php
// Show debug messages
define ('DEBUG', true);

// Database connection
define('DB_NAME', 'dbname');
define('DB_USER', 'dbuser');
define('DB_PASSWORD', 'dbpass');
define('DB_HOST', 'localhost');

// Website URL and path
define('PATH', 'http://www.example.com/');
define('WEBSITE_TITLE', 'Example.com');

// Default controller to load — homepage requests are sent to this controller
define('DEFAULT_CONTROLLER', 'home'); 

Routing

All routes in the framework are in the following format: controller_name/method_name. For example, if you open the web page http://www.example.com/home/welcome, the framework would execute the method named "welcome" located inside the "home" controller. This process is executed by router.php inside the core folder:

<?php
class Router {
	
	public function route($url) {
	    // Split the URL into parts
	    $url_array = array();
	    $url_array = explode("/",$url);
	    
                    // The first part of the URL is the controller name
	    $controller = isset($url_array[0]) ? $url_array[0] : '';
	    array_shift($url_array);

                    // The second part is the method name
	    $action = isset($url_array[0]) ? $url_array[0] : '';
	    array_shift($url_array);

                    // The third part are the parameters
	    $query_string = $url_array;
	 
	    // if controller is empty, redirect to default controller
	   if(empty($controller)) {
	       $controller = default_controller();
                   }
		
	    // if action is empty, redirect to index page
	    if(empty($action)) {
	        $action = 'index';
	    }
	 
	    $controller_name = $controller;
	    $controller = ucwords($controller);
	    $dispatch = new $controller($controller_name,$action);
	 
	    if ((int)method_exists($controller, $action)) {
	        call_user_func_array(array($dispatch,$action),$query_string);
	    } else {
	        /* Error Generation Code Here */
	    }
	}
	
}

Before explaining a sample controller class, let's see the two classes which are needed for any controller. The classes are Application and Controller. Application is the class that executes important processes before the controller is created. In this case, it sets proper error reporting values, unregisters globals and escapes all input (GET, POST and COOKIE variables):

<?php

class Application {
	
	function __construct() {
		$this->set_reporting();
		$this->remove_magic_quotes();
		$this->unregister_globals();
	}
	
	private function set_reporting() {
		if (DEVELOPMENT_ENVIRONMENT == true) {
	    error_reporting(E_ALL);
	    ini_set('display_errors','On');
		} else {
		    error_reporting(E_ALL);
		    ini_set('display_errors','Off');
		    ini_set('log_errors', 'On');
		    ini_set('error_log', ROOT.DS.'tmp'.DS.'logs'.DS.'error.log');
		}
	}
	
	private function strip_slashes_deep($value) {
    	$value = is_array($value) ? array_map(array($this,'strip_slashes_deep'), $value) : stripslashes($value);
    	return $value;
	}
	
	private function remove_magic_quotes() {
		if ( get_magic_quotes_gpc() ) {
		    $_GET    = $this->strip_slashes_deep($_GET);
		    $_POST   = $this->strip_slashes_deep($_POST);
		    $_COOKIE = $this->strip_slashes_deep($_COOKIE);
		}
	}
	
	private function unregister_globals() {
	    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]);
	                }
	            }
	        }
	    }
	}
}

The Controller class is a generic controller class that inherits the Application class and contains important functions for communicating with models and views:

<?php

class Controller extends Application {
	
    	protected $controller;
   	protected $action;
	protected $models;
	protected $view;
	
	public function __construct($controller, $action) {
		parent::__construct();
		
		$this->controller = $controller;
       		 $this->action = $action;
		$this->view = new View();
	}
	
	// Load model class
	protected function load_model($model) {
		if(class_exists($model)) {
			$this->models[$model] = new $model();
		}		
	}
	
	// Get the instance of the loaded model class
	protected function get_model($model) {
		if(is_object($this->models[$model])) {
			return $this->models[$model];
		} else {
			return false;
		}
	}
	
	// Return view object
	protected function get_view() {
		return $this->view;
	}
}

There are also generic classes for models and views:

<?php
class Model {	
	protected $db;	
	public function __construct() {
		$this->db = new MysqliDb(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
	}	
}

Since models only communicate with the database, the role of this class is to load the database class and make it available inside all models. The database class file is named sql.php and the class itself can be downloaded from here.

View class contains functions required for using PHP variables inside views and for rendering HTML code:

<?php

class View {
     
    protected $variables = array();
     
    function __construct() {

    }
 
    function set($name,$value) {
        $this->variables[$name] = $value;
    }
     
    function render($view_name) {
        extract($this->variables);
		
		if( file_exists(ROOT . DS . 'application' . DS . 'views' . DS . $view_name . '.php') ) {
			include (ROOT . DS . 'application' . DS . 'views' . DS . $view_name . '.php');
		} else {
			/* throw exception */						
		}
    }
 
}

Creating Your First Model, View and Controller

Let's create the first controller in this tutorial and call its class "Item." So, the filename will be item.php and it will be stored in application/controllers folder:

<?php

class Item extends Controller {
	
	public function __construct($controller,$action) {
		// Load core controller functions
		parent::__construct($controller, $action);
		
		// Load models
		$this->load_model('CategoryModel');
		$this->load_model('ItemModel');
	}
	
	public function index() {
		// Load search page
		$this->search();
	}
	
	public function search()  {
		// Call a model function
		$items = $this->get_model('ItemModel')->getAll();		
		
		// Set view variables
$this->get_view()->set('items', $items);

// Render view
		$this->get_view()->render('item/item_list_view');
	}
}

Each controller must inherit the core Controller class. The models are loaded for use inside a controller using the load_model function. Multiple models can be used in a controller.

The Index function is triggered when the following URL is requested: http://www.example.com/item/ (no 2nd parameter). In this case, we will just load the search function.

Models are created in application/models folder. The ItemModel class filename will be ItemModel.php:

<?php

class ItemModel extends Model {
	
	public function __construct() {
		// Load core model functions
		parent::__construct();
	}
	
	public function getAll() {
		// Return the database query using Mysqlidb database class
		return $this->db->get('item');
	}
	
}

After creating a model and a controller, let's create a view named item_list_view.php:

<?php 
	$this->render('common/meta'); 
	$this->render('common/header');
	
?>

<div>
	<?php foreach($items as $item): ?>
		<tr>
			<td><?php echo $item->id; ?></td>
			<td><?php echo $item->name; ?></td>
		</tr>
	<?php endforeach; ?>
</div>

<?php
	$this->render('common/footer');
?> 

Views are store inside application/views folder. Note that other views can be rendered inside the main view, which allows you to have partial views, such as header and footer.

Conclusion

We have just created a basic MVC framework. You can now see the theory from the first paragraph in practice — controller does all the programming and calls the required models and views, models execute the database operations, and views only show HTML code (with the required PHP variables inserted).



   
Vojislav is a web developer, designer and entrepreneur, based in Belgrade, Serbia. He has been working as a freelancer for more than 6 years, having completed more than 50 projects for clients from all over the worlds, specializing in designing and developing personal portfolios and e-commerce websites using Laravel PHP framework and WordPress content management system. Right now, he works as a full-time senior web developer in a company from Copenhagen.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date