Object-Oriented PHP Development Based On The MVC Model
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:
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.
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', '&');
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
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.

