WebIssues/MVC Pattern

From MiMec
Jump to: navigation, search

Introduction to MVC

The architecture of many modern web applications is based on a common MVC pattern consisting of three layers:

  • The Model represents the information on which the application operates its business logic.
  • The View renders the model into a web page suitable for interaction with the user.
  • The Controller responds to user actions and invokes changes on the model or view as appropriate.

Each layer can be further divided. The model usually consists of a database abstraction layer and a data access and manipulation layer. The view consists of a layout common to all pages, templates of individual pages and logic which glues these two together. The controller contains a front controller, which does the work common to all controllers, and actions which contain only the controller code specific to one page.

An example of a framework implementing such model is symfony, which I took as a reference because it has very good documentation and clean code. There are however some important differences between the architecture of symfony-based applications and the design of the WebIssues Server and its custom framework.

Application

The WebIssues Server doesn't have a single entry point. The URLs point to individual scripts, for example:

www.example.com/client/issues/addcomment.php?issue=5

Still a single object must implement logic common to all pages and act as the front controller. In WebIssues server this object is called the application and it executes the appropriate page action and outputs the rendered view. The application class can be extended to implement special behavior for a group of pages, for example to assign a common decorator to them or to implement security for the administration area.

The application also creates and holds references to other objects providing information about the current context of execution, such as the request, response, session, logger, etc. The reference to the global application object is available anywhere in the code; in addition references to frequently used components such as the request and response objects are available as members of all components and views.

Components and Views

Each WebIssues page consists of a single class implementing its logic and an associated view template. They are stored in two files with the same location and names, but different extension, for example:

client/index.php
client/index.html.php

Usually to render a single page more that one template is needed. A page template can include other templates, for example common controls or blocks which can appear on multiple pages. It can also be decorated with another template, for example page content can be decorated with the layout template common to all pages.

The common parts and decorators also consist of a logic layer and a view layer, in a similar way as pages. The WebIssues framework introduces a concept of a component, which is an object implementing some logic which is associated with a template. A page is simply a special case of a component which can be accessed through a URL. The template associated with a page can use other components and their templates can subsequently use their own components.

The example object tree of an application can look as follows:

Application
|
+- PageComponent
   |
   +- PageView
      |
      +- DecoratorComponent
      |  |
      |  +- DecoratorView
      |
      +- PartComponent
         |
         +- PartView
            |
            +- SubPartComponent
               |
               +- SubPartView

A decorator acts just like a regular component, except that its content is not inserted into the containing view, but rather the content of the containing view is inserted into an appropriate placeholder within the decorator. This allows for example to wrap page content in a layout or to wrap a block in a frame. So the final content of the rendered page will look as follows:

+- DecoratorComponentTemplate ------------------------------------------------------+
|                                                                                   |
|        +- PageTemplate ------------------------------+                            |
|        |                                             |                            |
|        |   +- PartComponentTemplate ----------+      |                            |
|        |   |                                  |      |                            |
|        |   |  +- SubPartComponentTemplate -+  |      |                            |
|        |   |  |                            |  |      |                            |
|        |   |  |                            |  |      |                            |
|        |   |  +----------------------------+  |      |                            |
|        |   |                                  |      |                            |
|        |   +----------------------------------+      |                            |
|        |                                             |                            |
|        |                                             |                            |
|        +---------------------------------------------+                            |
|                                                                                   |
+-----------------------------------------------------------------------------------+

Information can be passed to sub-components and decorators using a very simple mechanism of slots.

Sample Page

The implementation of a sample page looks like this:

<?php

require_once( 'system/bootstrap.inc.php' );

class Index extends System_Web_Page
{
    protected function __construct()
    {
        parent::__construct();
    }

    protected function execute()
    {
        $this->view->setDecoratorClass( 'Common_MessageBlock' );
        $this->view->setSlot( 'page_title', $this->tr( 'Hello, world!' ) );

        $this->user = 'MiMec';
    }
}

bootstrap( 'Common_Application', 'Index' );

The bootstrap.inc.php file is the main file of the WebIssues framework which initializes the environment and provides a function for creating and running the application.

All other files are included automatically using the PHP __autoload mechanism. For that to work, all classes must be declared in files whose path (relative to the root WebIssues directory) matches the class name, except that underscores are replaced with slashes, all letters are converted to lowercase and the .inc.php extension is appended. For example a class named System_Web_Page is declared in a file named system/web/page.inc.php. This convention also helps prevent name conflicts.

The bootstap() function creates the object of the given application class and optionally passes the second parameter to its constructor. Then it calls the run() method of the application. In the example above, Common_Application is a common application class used by all regular web pages. It derives System_Web_Application which implements the front controller of the MVC pattern and overrides its preparePage() method, setting default parameters such as the main layout decorator and default page title. The second parameter passed to bootstrap() is the name of the page which the application will in turn create and execute.

The page class must implement at least the execute() method which should contain the logic of the page action. It may read data from the request, interact with the models, set parameters of the response and define data items which will be accessible to the view template (by using PHP overloaded members mechanism).

A view template associated with this page may look like this:

<?php if ( !defined( 'WI_VERSION' ) ) die( -1 ); ?>

<h1>Welcome</h1>

<p>Hello, <?php echo $user ?>!</p>

The data items are accessible directly as local variables. The view object is also accessible using the $this variable and it provides access to the request, response, etc.

The template should use the alternative syntax and should generally contain as few PHP code as possible, as all logic should be handled by the page class. For compatibility short tags such as <?=$user?> should not be used.

The check for WI_VERSION prevents executing the template file directly by typing its name in the URL. It's only defined if the bootstap.inc.php file was already included by another script. Generally all PHP files should either include bootstap.inc.php or have this check in the first line.

There are a few other useful constants available:

  • WI_SCRIPT_PATH is the full path of the entry script
  • WI_ROOT_PATH is the path of the directory where WebIssues is installed
  • WI_SCRIPT_URL is the full URL of the entry script
  • WI_BASE_URL is the URL of WebIssues root directory

Always use WI_ROOT_PATH for loading or including files and use WI_BASE_URL for creating absolute links within the application.

Non-MVC Applications

Not all WebIssues applications have to use the MVC pattern. For example, the server handler and the cron job will use a custom mechanism of executing actions without using components, pages and views. So the main part of the WebIssues framework is divided into two modules: core and web. The core module implements a basic context of execution, request and response objects, session handling, etc., while the web module implements the MVC pattern and various classes making it easier to render and process web pages.

If you don't want to use the MVC pattern, simply define an application deriving System_Core_Application and implement the execute() method. Then call the bootstrap() function with the name of the application class and an optional parameter which can have any meaning.