Simple MVC framework Tutorial Part 1

Welcome to Simple MVC framework tutorial!

In this tutorial, I will show you how to build a very simple calculator application using MVC architecture pattern. This calculator only has addition and subtraction, and you will be able to extend this basic application by adding multiplication and division as exercise yourself! Before we start, I would like to talk briefly what MVC is and why we use it.

Why MVC?

If you are familiar with PHP, and you are about to develop or is currently developing a web application/site, you will probably notice how messy it can get. There is probably one PHP file for each page of your site.

Imagine you made a change to the name of one of your functions which you used in almost all your page. That’s right, you will have to go through every PHP file and change them. Not to mention how much code duplication you are having at the moment, which could be cut down to make your project more compact,
clean, and easy to understand by yourself or others. This is part of the problems I was having when I started
coding medium size project, and that’s why I was searching for a solution, and I found the concept of MVC.

MVC can help you reduce code duplication, better organize your source code files, reduce development time and make some components reusable in another MVC project.

So What is MVC in practice?

From Wikipedia Model–View–Controller (MVC) is an architectural pattern used in software engineering. Successful use of the pattern isolates business logic from user interface considerations, resulting in an application where it is easier to modify either the visual appearance of the application or the underlying business rules without affecting the other. In MVC, the model represents the information (the data) of the application; the view corresponds to elements of the user interface such as text, checkbox items, and so forth; and the controller manages the communication of data and the business rules used to manipulate the data to and from the model.

In simpler words-1. Model handles all our database logic. Using the model we connect to our database and provide an abstraction layer. 2. Controller represents all our business logic i.e. all our ifs and else. 3. View represents our presentation logic i.e our HTML/XML/JSON code.

Let’s go through the heart of the framework first!

Download the Simple MVC framework tutorial First of all, please download the basic framework attached and extract it to your root directory. it has this folder structure.
1
We might not use all the folders right at this moment, but we will in the future. I will then explain the code in the key files.

  • config – database/server configuration
  • controllers – your custom controllers
  • library – framework code
  • models- your custom models
  • public – application specific js/css/images
  • tmp – temporary data, for example, PHP errors
  • views – your custom client side templates

Coding Conventions

  1. Models will  always have “_Model” appended to them e.g. User_Model, Apple_Model
  2. Controllers will always have “_Controller” appended to them. e.g. User_Controller, Apple_Controller
  3. Views will have singular name followed by action name as the file. e.g. user/add.php, apple/buy.php

We first add .htaccess file in the root directory which will redirect all calls to the public folder

[sourcecode language=”xml”]
RewriteEngine on
RewriteRule ^$ /public/ [L]
RewriteRule (.*) /public/$1 [L]
[/sourcecode]

We then add .htaccess file to our public folder which redirect all calls to index.php. Line 3 and 4 make sure that the path requested is not a filename or directory. Line 7 redirects all such paths to index.php/PATHNAME

[sourcecode language=”xml”]
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?url=$1 [L,PT]
[/sourcecode]

This redirection has many advantages- a) we can use it for bootstrapping i.e. all calls go via our index.php except for images/js/cs. b) we can use pretty/seo-friendly URLS c) we have a single entry point Now we add index.php to our public folder

[sourcecode language=”php”]
<?php
define(‘DS’, DIRECTORY_SEPARATOR);
define(‘ROOT’, dirname(dirname(__FILE__)));

$url = isset($_GET[‘url’]) ? $_GET[‘url’] : null ;

require_once (ROOT . DS . ‘config’ . DS . ‘config.php’);
require_once( ROOT . DS . ‘library’ . DS . ‘router.php’);

[/sourcecode]

Notice that I have purposely not included the closing ? >. This is to avoid injection of any extra whitespaces in our output. For more, I suggest you view Zend’s coding style.

Our index.php basically set the $url variable and calls config.php and router.php which reside in our config and library directories.

Now lets view our config.php

[sourcecode language=”php”]
<?php
/** Development Environment **/
// when Set to false, no error will be throw out, but saved in temp/log.txt file.
define (‘DEVELOPMENT_ENVIRONMENT’,true);

/** Site Root **/
// Domain name of the site (no slash at the end!)
//define(‘SITE_ROOT’ , ‘http://You domain name’);
define(‘SITE_ROOT’ , ‘http://169.0.0.1’);
define (‘DEFAULT_CONTROLLER’, “index”);
define (‘DEFAULT_ACTION’, “index”);
[/sourcecode]

Now let us have a look at router.php, finally something that does some real work

[sourcecode language=”php”]
<?php
/** Check if environment is development and display errors **/
function SetReporting() {
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’ );
}
}

/** 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]);
}
}
}
}
}

//Automatically includes files containing classes that are called
function __autoload($className) {
//fetch file
if(file_exists( ROOT . DS . ‘controllers’ . DS . strtolower($className) . ‘.php’) ) {
require_once( ROOT . DS . ‘controllers’ . DS . strtolower($className) . ‘.php’);
}
else if (file_exists( ROOT . DS . ‘models’ . DS . strtolower($className) . ‘.php’) ) {
require_once( ROOT . DS . ‘models’ . DS . strtolower($className) . ‘.php’); }
else if (file_exists( ROOT . DS . ‘library’ . DS . strtolower($className) . ‘.php’) ) {
require_once( ROOT . DS . ‘library’ . DS . strtolower($className) . ‘.php’);
}
else {
// Error: Controller Class not found
die(“Error: Class not found.”);
}
}

/** Main Call Function **/
function CallHook() {
global $url;
if (!isset($url)) {
$controllerName = DEFAULT_CONTROLLER; $action = DEFAULT_ACTION;
}
else {
$urlArray = array();
$urlArray = explode(“/”,$url);
$controllerName = $urlArray[0];
$action = (isset($urlArray[1]) && $urlArray[1] != ”) ? $urlArray[1] : DEFAULT_ACTION;
}

$query1 = (isset($urlArray[2]) && $urlArray[2] != ”) ? $urlArray[2] : null;
$query2 =(isset($urlArray[3]) && $urlArray[3] != ”) ? $urlArray[3] : null;

//modify controller name to fit naming
convention $class = ucfirst($controllerName).’_Controller’;

//instantiate the appropriate class
if(class_exists($class) && (int)method_exists($class, $action)) {
$controller = new $class;
$controller->$action($query1, $query2);
//call_user_func_array(array($controller,$action),$query1);
}
else {
//Error: Controller Class not found
die(“1. File <strong>’$controllerName.php'</strong> containing class <strong>’$class'</strong> might be missing. 2. Method <strong>’$action'</strong> is missing in <strong>’$controllerName.php'</strong>”);
}
}

SetReporting();
RemoveMagicQuotes();
UnregisterGlobals();
CallHook();

[/sourcecode]

Let me explain the above code briefly. The setReporting() function helps us display errors only when the DEVELOPMENT_ENVIRONMENT is true.
The next move is to remove global variables and magic quotes. Another function that we make use of is __autoload which helps us load our classes automagically.
Finally, we execute thecallHook() function which does the main processing.

First let me explain how each of our URLs will look – yourdomain.com/controller/action/query
So callHook() basically takes the URL which we have received from index.php and separates it out as
$controller, $action and the remaining as $query1 and $query2 (if exists).
$model is the singular version of $controller. e.g. if our URL is todo.com/items/view/1/first-item, then  controller is items, Action is view, Query1 is 1, and Query2 is first-item After the separation is done,
it creates a new object of the class $controller.”Controller” and calls the method $action of the class.
Now let us create a few classes first namely our base Controller class which will be used as the base class for all our controllers, our Model class which will be used as base class for all our models.
First the controller.php in library folder

[sourcecode language=”php”]
<?php
class Controller {

protected $view;
protected $model;
protected $view_name;
public function __construct() {
$this->view_name = ”;
$this->view = new View();
}

public function index(){
$this->Assign(‘content’, ‘This is index class index method, Method is not set yet.’);
}

function Assign($variable, $value) {
$this->view->Assign($variable, $value);
}

function Load_Model($name){
$modelName = $name . ‘_Model’;
$this->model = new $modelName();
}

function Load_View($name){
if(file_exists( ROOT . DS . ‘views’ . DS . strtolower($name) . ‘.php’)){
$this->view_name = $name;
}
}

public function __destruct() {
if(!empty($this->view_name)){
$this->view->Render($this->view_name);
}
}

}

[/sourcecode]

The above class is used for all communication between the controller, the model and the view (template class).

It creates an object for the model class and an object for template class.
The object for model class has the same name as the model itself, so that we can call it something like $this->Item->selectAll(); from our controller.

We can then use Assign method to pass the data from model to View, which is the template.
Finally while destroying the class we call the?render()?function which displays the view (template) file. Now let us look at our model.php

[sourcecode language=”php”]
<?php
class Model {

}
[/sourcecode]

The Model parent class is empty at the moment because we are not connecting to any database.

You can use it to extends the SQLQuery class which basically is an abstraction layer for the mySQL connectivity.

Depending on your requirements you can specify any other DB connection class that you may require.

Now let us have a look at view.php

[sourcecode language=”php”]
<?php

/** * Handles the view functionality of our MVC framework */
class View {

private $data = array();
private $render = FALSE;
public function __construct() {

$this->data[‘site_title’] = ”;
$this->data[‘site_icon’] = ”;
$this->data[‘meta_description’] = ”;
$this->data[‘meta_keywords’] = ”;

$this->data[‘css’] = ”;
$this->data[‘js’] = ”;

$this->data[‘header’] = ”;
$this->data[‘content’] = ”;
$this->data[‘footer’] = ”;

}

public function Assign($variable = ”, $value) {
if ($variable == ”)
$this->data = $value;
else
$this->data[$variable] = $value;
}

public function Render($view, $direct_output = TRUE) {

if(substr($view, -4) == “.php”){
$file = $view;
}
else{
$file = ROOT . DS . ‘views’ . DS . strtolower($view) . ‘.php’;
}

if (file_exists($file)) {

/**
* trigger render to include file when this model is destroyed
* if we render it now, we wouldn’t be able to assign variables
* to the view!
*/
$this->render = $file;
}
else{
return “view file doesn’t exist.”;
}

// Turn output buffering on, capturing all output
if ($direct_output !== TRUE) {
ob_start();
}

// Parse data variables into local variables
$data = $this->data;

// Get template
include($this->render);

// Get the contents of the buffer and return it
if ($direct_output !== TRUE) {
return ob_get_clean();
}
}

public function Set_Site_Title($name){
$this->data[‘site_title’] = ” . $name . ”;
}

public function Set_Site_Icon($filename){
if (file_exists(SITE_ROOT . DS . $filename)){
$this->data[‘site_icon’] = ‘ <link href=”‘ . SITE_ROOT . DS . $filename . ‘” rel=”shortcut icon” type=”image/vnd.microsoft.icon” />’;
}
}

public function Set_Meta_Keywords($words){
$this->data[‘meta_keywords’] = ‘<meta name=”keywords” content=”‘ . $words . ‘” />’;
}

public function Set_Meta_Description($descr){
//$this->data[‘meta_description’] = ‘<meta name=”description” content=”‘ . $descr . ‘” />’;
}

public function Set_CSS($filename){
if (file_exists(ROOT . DS . $filename)){
$this->data[‘css’] = $this->data[‘css’] . ‘ <link href=”‘ . SITE_ROOT . ‘/’ . str_replace(”, ‘/’, $filename) . ‘” rel=”stylesheet” type=”text/css” />’;
}
}

public function Set_JS($filename){
$this->data[‘js’] = $this->data[‘js’] . ‘<script type=”text/javascript” src=”‘ . $filename . ‘” language=”JavaScript”></script>’ . PHP_EOL; }
}

[/sourcecode]

The above code is pretty straight forward. Just one point- if it does not find header and footer in theview/controllerName folder then it goes for the global header and footer in the view folder.

Now all we have to add is a config.php in the config folder and we can begin creating our first model, view and controller!

Start Coding!

Before you start, make sure you open “/config/config.php” file, ?and change the SITE_ROOT variable to your own domain name. Let’s display something using controller. Create a file in the controllers folder, called index_controller.php

[sourcecode language=”php”]

<?php
class Index_Controller extends Controller{

public function __construct() {
parent::__construct();
}

public function index() {
echo( “123”);
}

}

[/sourcecode]

You should see “123” on the screen. Next, let’s write a model in the models folder, and name it “math_model.php” to do some data manipulations, and pass to controller.

[sourcecode language=”php”]
<?php

class Math_Model extends Model {

function __construct() {
parent::__construct();
}

public function Add($var1, $var2) {
return $var1 + $var2;
}

public function Sub($var1, $var2) {
return $var1 – $var2;
}

}

[/sourcecode]

And let’s modify the Index_Controller to use the model we just wrote,

[sourcecode language=”php”]
<?php
class Index_Controller extends Controller{
public function __construct() {
parent::__construct();
}

public function index() {
$var1 = 6;
$var2 = 3;
$math = new Math_Model();
echo($var1 . ” Add ” . $var2 . ” is ” . $math->Add($var1, $var2) . “, ” . $var1 . ” Subtract ” . $var2 . ” is ” . $math->Sub($var1, $var2));
}

}

[/sourcecode]

The output should be “6 Add 3 is 9, 6 Subtract 3 is 3”. Okay, we now know how to use model and controller, we can move on and learn how to use view, to apply some html template. First let’s create a folder in “views” folder and call it “index”, and create a file called “index.php” in it.

[sourcecode language=”html”]
<h1>Title</h1>
Body
[/sourcecode]

Okay, let’s try to load this view file from our index controller by adding a few lines:

[sourcecode language=”php”]
<?php
class Index_Controller extends Controller{
public function __construct() {
parent::__construct();
}
public function index() {
$var1 = 6;
$var2 = 3;
$math = new Math_Model();
echo($var1 . ” Add ” . $var2 . ” is ” . $math->Add($var1, $var2) . “, ” . $var1 . ” Subtract ” . $var2 . ” is ” . $math->Sub($var1, $var2));

$content_view = new View();
$content_view->Render(“index”.DS.”index”);
}

}
[/sourcecode]

And you get the title and body shows up in its HTML format! Okay, let’s modify this view template and index controller a little bit, and try to pass data from the controller to the view.

[sourcecode language=”php”]
<h1><?=$data[“title”];?> </h1>
<?=$data[“body”];?>
[/sourcecode]

Okay, let’s try to load this view file from our index controller by adding a few lines:

[sourcecode language=”php”]
<?php
class Index_Controller extends Controller{
public function __construct() {
parent::__construct();
}
public function index() {
$var1 = 6;
$var2 = 3;
$math = new Math_Model();
echo($var1 . ” Add ” . $var2 . ” is ” . $math->Add($var1, $var2) . “, ” . $var1 . ” Subtract ” . $var2 . ” is ” . $math->Sub($var1, $var2));

$content_view = new View();
$content_view->Render(“index”.DS.”index”);
}

}
[/sourcecode]

And you get the title and body shows up in its HTML format! Okay, let’s modify this view template and index controller a little bit, and try to pass data from the controller to the view.

[sourcecode language=”php”]
<h1><?=$data[“title”];?></h1>
<?=$data[“body”];?>
[/sourcecode]
[sourcecode language=”php”]
<?php
class Index_Controller extends Controller{
public function __construct() {
parent::__construct();
}

public function index() {
$var1 = 6;
$var2 = 3;
$math = new Math_Model();
$content_view = new View();
$content_view->Assign(“title”, “Add”);
$content_view->Assign(“body”, $var1 . ” Add ” . $var2 . ” is ” . $math->Add($var1, $var2));
$content_view->Render(“index”.DS.”index”);

$content_view = new View();
$content_view->Assign(“title”, “Subtract”);
$content_view->Assign(“body”, $var1 . ” Subtract ” . $var2 . ” is ” . $math->Sub($var1, $var2));
$content_view->Render(“index”.DS.”index”);

}

}
[/sourcecode]

And you get this:
Simple MVC framework tutorial
So, that’s it for Part one of the Simple MVC framework tutorial. You have learnt how to use Model, View, and Controller to display information. In the next part, I will develop this application further, so user can enter input. Also I will add some more methods and controller to make it more fun! Stay tuned. Feeling lazy? Download link here Download Framework Part 1 (ZIP File) Suggestions? Do let me know your suggestions on how we can improve this code/any particular features you would like to see implemented/any other unrelated topic you would like a tutorial on.

15 thoughts on “Simple MVC framework Tutorial Part 1

  1. David

    Hy,

    Nice tutorial, but in the zip file, the libary/model.php opens with a sort tag. It is the only file. Because of that I’ve spent 10-15 minutes of debugging why I’m getting fatal error: Model calss not found…

    Reply
  2. Salam

    This is excellent and strong. I wanted to pay you. But only through Skrill. I do not know whether you use skrill.

    Reply
  3. ritesh sharma

    1. File index.php containing class indexcontroller might be missing. 2. Method index is missing in index.php.

    I am getting this error

    using newtbeans, zend xampp on windiws 7

    Reply
  4. chandrageetha

    I couldnt download the zip file as it doesnt work. the code is not working for my server localhost. it shows some error. more over, this tutorial in text format on web page missing css and other files. so, please look into it or send me the source code by mail.

    Reply
  5. Johan Hultin

    Hello, lovely tutorial.

    Not much new in it, but I find it to be a good learning experience to go through other peoples code, especially when explained. And as I am in the process of making my own MVC framework I ended up here.

    One thing I did notice that makes little to no sense to me is define(‘SITE_ROOT’ , ‘http://169.0.0.1’); I have in my own framework achieved the same thing much more dynamically by doing it this way: define(‘ROOT’, dirname(__FILE__));

    However, since you are by far a better php developer (for the time being ;)) than me, I’d like your input on this.

    Thanks for a great tutorial

    /Johan Hultin

    Reply
    1. Oscar

      Hi,
      you might have seen a lot of people using dirname(__FILE__) to refer to the absolute path root.
      but i think it’s more efficient to just use the IP address rather then checking it everytime you run the “define” function.

      the efficiency gain might be very small, but that’s just me :-D
      You are right, using dirname(__FILE__) is more flexible, and it’a totally a personal coding style choice.

      Reply
  6. Iain

    Thank you Oscar. I’ll download it and compare the code with what I have.

    I know that code can get mangled when it’s posted in a blog like this and it’s no-one’s fault. I’ve tried to debug the errors that crept in but failed miserably!

    I’m grateful for your time in creating these tutorials.

    Reply
  7. Iain

    Hi Oscar

    I’m having a hard time getting the code to work without generating errors. I have added:

    error_reporting(E_ALL);
    ini_set(“display_errors”, 1);

    at the beginning of index.php to display the errors.

    I see there is a stray “—-” in router.php and there’s a stray “convention” at the beginning of line 78 of the same file. I’ve not seen any other obvious problems, but there must be, otherwise I’d be able to get the output that you have described.

    I’d really like to get this to work properly so I can follow this, as well as your second tutorial. Do you have the code available for download?

    Reply
  8. Jason Demitri

    Hi just created the above tutorial however getting no read out when entering the server root via either localhost or /127.0.0.1 and it seem silly for me to me get carry on without finding were i have gone wrong ? I have noticed there is no download link for the ZIP file as mentioned maybe if u could provide that I could compare what ive written to what’s on the ZIP. Many Thanks Great Tutorial So Far

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you Robot? *

I don't look at blog comments very often (maybe once or twice a week), so if you have any questions related to multirotor please post it on this forum IntoFPV.com... You're likely to get a response from me faster on there.