Object-Oriented PHP Development Based On The MVC Model

Posted on January 17, 2009 by Vagharshak Tozalakyan

MVC (Model-View-Controller) is a popular architectural and design pattern widely used in software engineering. The main aim of the MVC architecture is to separate the business logic and application data from the presentation data to the user. The usage of MVC model in connection with PHP's object-oriented features allows to build scalable, reusable and expressive Web applications.

In this article we are going to construct a lightweight MVC framework and see how it may be possible to adapt the MVC model to the nature of Web applications.

Take a look at the basic workflow of a typical MVC-based Web application:

MVC Workflow

When a user interacts with a program he(she) is dealing with the Controller. Basically, the Controller is the main start point (the gateway) of an application. It controls the rest workflow based on a particular decision of a user. The Controller should not implement any business logic, it simply dispatches the task request to the Model, which will process the business logic and will return the result data back to the Controller. After having processed results the Controller activates the View, which is responding for the presentation of the data to the user.

If we construct our application on the source code level to follow such kind of structure, we shall be able to control the application's infrastructure, modular business rules and on-screen design separately from each other. As a result the application will become more scalable and more easy to maintain.

Here are the PHP interfaces for the main 3 parts of our application: the Controller, the Model and the View:

./controller/interface.Application.php

<?php

interface IApplication
{
    public function 
init();
    public function 
run();

}

?>

./model/interface.Model.php

<?php

interface IModel
{
    public function 
__construct(&$application);
    public function 
authorize();
    public function 
process();
    public function 
getData();

}

?>

./view/interface.View.php

<?php

interface IView
{
    public function 
__construct(&$application, &$model);
    public function 
show();

}

?>

The block scheme below shows how the interface implementations will interact each other.

Workflow Block Scheme

The application startes by executing the init() method. It is the place where we can perform some application-level initialization tasks, e.g. connect the database, start a session, define languages, etc. The main part of the application is executed whithin the run() method. Here we create the Model, check if the user have appropriate permissions for the Model, process the Model and create the View based on processed Model.

The only thing we should do in index.php is to crerate an Application (Controller) object and call it's init() and run() methods.

./index.php

<?php

require_once 'controller/class.MyApplication.php';

$app = new MyApplication();
$app->init();
$app->run();

?>

The MyApplication class in the code is a particular descendant of an IApplication implementation.

./controller/class.MyApplication.php

<?php

require_once 'controller/class.Application.php';

class 
MyApplication extends Application
{
}

?>

./controller/class.Application.php

<?php

require_once 'controller/interface.Application.php';

class 
Application implements IApplication
{
    ...

    public function 
init()
    {
        
$this->_connectDatabase();
        
$this->_startSession();
        
$this->_initLanguage();
        
$this->_initSelfUrl();
        
$this->_initTimes();
    }

    public function 
run()
    {
    }

}

?>

We may see 5 predefined initialization methods in our basic Application class. Some or all of these methods may be overrided in the Application descendant (MyApplication class). In addition, if we need more initialization methods we always can override the method init() itself.

./controller/class.Application.php

<?php

require_once 'controller/interface.Application.php';

class 
Application implements IApplication
{
    ...

    protected 
$_selfUrl '';
    protected 
$_now '';
    protected 
$_sqlNow '';

    protected function 
_connectDatabase()
    {
    }

    protected function 
_startSession($name '')
    {
        if (!empty(
$name)) {
            
session_name($name);
        }
        
session_start();
    }

    protected function 
_initLanguage()
    {
    }

    protected function 
_initSelfUrl()
    {
        
$this->_selfUrl self::buildGoUrl($this->_go);
    }

    protected function 
_initTimes()
    {
        
$this->_now time();
        
$this->_sqlNow date('Y-m-d H:i:s'time());
    }

    public static function 
buildGoUrl($go)
    {
        
$url 'http://';
        if (isset(
$_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'on') {
            
$url 'https://';
        }
        
$url .= $_SERVER['HTTP_HOST'];
        
$url .= dirname($_SERVER['PHP_SELF']);
        
$url trim(str_replace('\\''/'$url));
        if (
substr($url, -1) != '/') {
            
$url .= '/';
        }
        if (!empty(
$go)) {
            
$url .= '?go=' $go;
        }
        return 
$url;
    }

    public function 
init()
    {
        
$this->_connectDatabase();
        
$this->_startSession();
        
$this->_initLanguage();
        
$this->_initSelfUrl();
        
$this->_initTimes();
    }

    public function 
run()
    {
    }

}

?>

Look at the static method called buildGoUrl(). This method could be used to costruct URL strings like "http://mysite.com/?go=ModuleName". Draw attention that we have the only index.php which controls the rest of processing tasks, therefore the Controller needs to know "where to go" to dispatch the request to the Model.

In the next 2 listings you can see the settings class which is keeping constant parameters of our application and the MyApplication class (a project-related descendant of the basic Application class) with overriden methods to connect the MySql database, init a database session handler and to define the language options.

./controller/class.Settings.php

<?php

class Settings
{
    const 
dbType 'MySql';
    const 
dbHost 'localhost';
    const 
dbPort '';
    const 
dbName 'mydatabase';
    const 
dbUser 'root';
    const 
dbPassword '';

    const 
template 'default';

    const 
defaultLanguage 'en-us';
    const 
allowChangeLanguage true;

    const 
timeDifference 0;

}

?>

./controller/class.MyApplication.php

<?php

require_once 'controller/class.Application.php';
require_once 
'controller/class.Settings.php';
require_once 
'library/class.DatabaseFactory.php';
require_once 
'library/class.SessionHandler.php';

class 
MyApplication extends Application
{
    protected function 
_connectDatabase()
    {
        try {
            
$this->_db DatabaseFactory::Create(Settings::dbType);
            
$this->_db->connect(
                
Settings::dbHost,
                
Settings::dbName,
                
Settings::dbUser,
                
Settings::dbPassword,
                
Settings::dbPort
            
);
        } catch (
Exception $e) {
            die(
$e->getMessage());
        }
    }

    protected function 
_startSession($name '')
    {
        try {
            new 
SessionHandler($this->_db'sessions');
        } catch (
Exception $e) {
            die(
$e->getMessage());
        }
        if (!empty(
$name)) {
            
session_name($name);
        }
        
session_start();
    }

    public static function 
isValidLanguage($language)
    {
        return 
is_dir('languages/' basename($language));
    }

    protected function 
_initLanguage()
    {
        if (
Settings::allowChangeLanguage) {
            if (isset(
$_GET['l'])) {
                
$_SESSION['l'] = $_GET['l'];
            } elseif (isset(
$_SESSION['l'])) {
                
$_SESSION['l'] = $_SESSION['l'];
            } elseif (isset(
$_COOKIE['l'])) {
                
$_SESSION['l'] = $_COOKIE['l'];
            } else {
                
$_SESSION['l'] = Settings::defaultLanguage;
            }
            if (!(
self::isValidLanguage($_SESSION['l']))) {
                
$_SESSION['l'] = Settings::defaultLanguage;
            }
            @
setcookie('l'$_SESSION['l'], time() + 0xd2f00);
        } else {
            
$_SESSION['l'] = Settings::defaultLanguage;
        }
        
$this->_language $_SESSION['l'];
    }

    protected function 
_initTimes()
    {
        
$this->_now time() + Settings::timeDifference;
        
$this->_sqlNow date('Y-m-d H:i:s'$this->_now);
    }

}

?>

Now let us digress a little from the Controller and take a look at the Model and View basic classes. Generally, we'll have a separate descendants of these classes for every module (a page, dispatched by the go parameter). For example, if we have a "Contact Us" module in our application, we may have a Model_Contact class (a Model descendant) and a View_Contact class (a View descendant). If there is no appropriate Model or View class for the go parameter, then the basic Model or View classes will be used.

./model/class.Model.php

<?php

require_once 'model/interface.Model.php';

class 
Model implements IModel
{
    protected 
$_application null;
    protected 
$_data = array();

    public function 
__construct(&$application)
    {
        
$this->_application $application;
    }

    public function 
authorize()
    {
        return 
true;
    }

    public function 
process()
    {
    }

    public function 
getData()
    {
        return 
$this->_data;
    }

}

?>

./model/class.View.php

<?php

require_once 'view/interface.View.php';

class 
View implements IView
{
    protected 
$_application null;
    protected 
$_model null;
    protected 
$_values = array();

    public function 
__construct(&$application, &$model)
    {
        
$this->_application $application;
        
$this->_model $model;
        
$this->_values['tplPath'] = 'templates/';
        
$this->_values['tplPath'] .= Settings::template '/';
    }

    public function 
show()
    {
        
ini_set('arg_separator.output''&amp;');
        
header('Content-Type: text/html; charset=utf-8');
        
$tpl $this->_values;
        require_once 
$this->_values['tplPath'] . 'tpl.Header.php';
        
$_tplFile 'tpl.' $this->_application->getGo() . '.php';
        if (
$this->_application->getGo() == '') {
            
$_tplFile 'tpl.Default.php';
        }
        @include_once 
$this->_values['tplPath'] . $_tplFile;
        require_once 
$this->_values['tplPath'] . 'tpl.Footer.php';
    }

}

?>

In this example we are using native PHP templates instead of a specialized template engine like Smarty. Due to a modular system of the MVC model you can easily modify the View to work with any template engine.

./templates/default/tpl.Header.php

<?php echo '<?xml version="1.0" encoding="utf-8"?>'?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="content-style-type" content="text/css" />
<meta http-equiv="content-script-type" content="text/javascript" />
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="expires" content="-1" />
<meta http-equiv="imagetoolbar" content="no" />
<meta name="keywords" content="" />
<meta name="description" content="" />

<link rel="stylesheet" type="text/css"
    href="<?php echo $tpl['tplPath']; ?>style.css" />
<link rel="shortcut icon" href="favicon.ico" />

<title><?php echo Language::title?></title>

</head>

<body>

./templates/default/tpl.Default.php

<h1><?php echo Language::hello?></h1>

./templates/default/tpl.Footer.php


</body>

</html>

The last thing we shall do is to create instances of appropriate Model and View classes within the run() method of the basic Application class.

./controller/class.Application.php

<?php

require_once 'controller/interface.Application.php';

class 
Application implements IApplication
{
    ...

    protected 
$_go '';
    protected 
$_db null;
    protected 
$_language '';

    ...

    public function 
run()
    {
        if (isset(
$_GET['go'])) {
            
$this->_go basename($_GET['go']);
        }

        if (!empty(
$this->_language)) {
            
$langClass =  'Language_' $this->_go;
            
$langFile 'languages/' $this->_language;
            
$langFile .= '/class.' $langClass '.php';
            if (!
file_exists($langFile)) {
                
$langClass =  'Language';
                
$langFile 'languages/' $this->_language;
                
$langFile .= '/class.' $langClass '.php';
            }
            require_once 
$langFile;
        }

        
$modelClass =  'Model_' $this->_go;
        
$modelFile 'model/class.' $modelClass '.php';
        if (!
file_exists($modelFile)) {
            
$modelClass =  'Model';
            
$modelFile 'model/class.' $modelClass '.php';
        }
        require_once 
$modelFile;
        
$model = new $modelClass($this);
        if (!
$model->authorize()) {
            die(
'You have no permission to view this page.');
        }
        
$model->process();

        
$viewClass =  'View_' $this->_go;
        
$viewFile 'view/class.' $viewClass '.php';
        if (!
file_exists($viewFile)) {
            
$viewClass =  'View';
            
$viewFile 'view/class.' $viewClass '.php';
        }
        require_once 
$viewFile;
        
$view = new $viewClass($this$model);
        
$view->show();
    }

}

?>

Example code for this article: framework.zip

Comments

View all comments (3)

Comments are moderated. If your comment does not appear immediately, there is no need to submit it again. Please treat others with respect. Comments containing hate speech, obscenity, and personal attacks will not be approved.






Validation code