Difference between revisions of "WebIssues/MVC Pattern"

From MiMec
Jump to: navigation, search
(Introduction to MVC)
 
(3 intermediate revisions by the same user not shown)
Line 9: Line 9:
 
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.
 
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 [http://www.symfony-project.org/ 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 it's custom framework.
+
An example of a framework implementing such model is [http://www.symfony-project.org/ 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.
  
== Multiple Entry Points ==
+
== Application ==
  
Many applications, including symfony, use URL rewriting so that all request are handled by a single entry point and the action to invoke is determined by the URL, for example:
+
The WebIssues Server doesn't have a single entry point. The URLs point to individual scripts, for example:
  
  www.example.com/article/view/123
+
  www.example.com/client/issues/addcomment.php?issue=5
  
URL rewriting also allows to use user-friendly URLs and make the dynamic application resemble a static website, for example:
+
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.
  
www.example.com/2009/07/introduction-to-mvc.html
+
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.
  
Such approach is not needed in case of an application like WebIssues, which doesn't need to be indexed by search engines and doesn't require user-friendly URLs. So the WebIssues framework will use traditional URLs, for example:
+
== Components and Views ==
  
www.example.com/client/viewfolder.php?id=10&page=4
+
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:
  
The advantage is that we don't need to set routing rules and configure mod_rewrite. It also makes WebIssues easier to deploy on Windows with IIS where URL rewriting is usually not available.
+
client/index.php
 +
client/index.html.php
  
== Page/View Coupling ==
+
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.
  
In many traditional MVC applications, including symfony, actions and views are not tightly coupled; the same view can be rendered by many different actions, and on the other hand, one action can by rendered using different views depending on the result (for example success/failure) or context.
+
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.
 
 
Though this is a powerful mechanism, it can also be difficult to manage and can make the code less clear. In WebIssues, each Page object implements a single action and has a single associated view template. Page logic and template are stored in two files with the same location and names, but different extension. This makes it easy to find a template related to a page and vice versa.
 
 
 
== Application ==
 
 
 
The WebIssues Server doesn't have a single entry point, but it still requires a single object which implements all logic common to all pages. This object is called the Application. It serves as the front controller which executes the appropriate page action and outputs the rendered view.
 
 
 
The application also creates and hold references to all other objects forming a single 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.
 
 
 
== Components ==
 
 
 
Usually to render a whole page more that one templates are needed. A view template can include other templates, for example common controls or blocks which 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, similar to pages. The WebIssues framework introduces a concept of a Component, which is an object associated with a view template. A Page is simply a special case of a Component and actually its subclass. The template associate 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:
 
The example object tree of an application can look as follows:
Line 49: Line 36:
 
  Application
 
  Application
 
  |
 
  |
  +- Page
+
  +- PageComponent
 
     |
 
     |
 
     +- PageView
 
     +- PageView
Line 55: Line 42:
 
       +- DecoratorComponent
 
       +- DecoratorComponent
 
       |  |
 
       |  |
       |  +- DecoratorComponentView
+
       |  +- DecoratorView
 
       |
 
       |
 
       +- PartComponent
 
       +- PartComponent
 
           |
 
           |
           +- PartComponentView
+
           +- PartView
 
             |
 
             |
 
             +- SubPartComponent
 
             +- SubPartComponent
 
                 |
 
                 |
                 +- SubPartComponentView
+
                 +- SubPartView
  
A decorator acts just like a regular component, except that it's contents is not inserted into the containing view, but rather the contents of the containing view is inserted in an appropriate placeholder of the decorator. This allows for example to wrap page contents in a layout or wrap block contents in a frame. So the final contents of the rendered page looks as follows:
+
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 ------------------------------------------------------+
 
  +- DecoratorComponentTemplate ------------------------------------------------------+
Line 84: Line 71:
 
  |                                                                                  |
 
  |                                                                                  |
 
  +-----------------------------------------------------------------------------------+
 
  +-----------------------------------------------------------------------------------+
 +
 +
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:
 +
 +
<pre>
 +
<?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' );
 +
</pre>
 +
 +
The <code>bootstrap.inc.php</code> 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 [http://php.net/__autoload __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 <code>.inc.php</code> extension is appended. For example a class named <code>System_Web_Page</code> is declared in a file named <code>system/web/page.inc.php</code>. This convention also helps prevent name conflicts.
 +
 +
The <code>bootstap()</code> function creates the object of the given application class and optionally passes the second parameter to its constructor. Then it calls the <code>run()</code> method of the application. In the example above, <code>Common_Application</code> is a common application class used by all regular web pages. It derives <code>System_Web_Application</code> which implements the front controller of the MVC pattern and overrides its <code>preparePage()</code> method, setting default parameters such as the main layout decorator and default page title. The second parameter passed to <code>bootstrap()</code> is the name of the page which the application will in turn create and execute.
 +
 +
The page class must implement at least the <code>execute()</code> 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 [http://php.net/__get overloaded members] mechanism).
 +
 +
A view template associated with this page may look like this:
 +
 +
<pre>
 +
<?php if ( !defined( 'WI_VERSION' ) ) die( -1 ); ?>
 +
 +
<h1>Welcome</h1>
 +
 +
<p>Hello, <?php echo $user ?>!</p>
 +
</pre>
 +
 +
The data items are accessible directly as local variables. The view object is also accessible using the <code>$this</code> variable and it provides access to the request, response, etc.
 +
 +
The template should use the [http://php.net/manual/en/control-structures.alternative-syntax.php 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 <code><?=$user?></code> should not be used.
 +
 +
The check for <code>WI_VERSION</code> prevents executing the template file directly by typing its name in the URL. It's only defined if the <code>bootstap.inc.php</code> file was already included by another script. Generally all PHP files should either include <code>bootstap.inc.php</code> or have this check in the first line.
 +
 +
There are a few other useful constants available:
 +
* <code>WI_SCRIPT_PATH</code> is the full path of the entry script
 +
* <code>WI_ROOT_PATH</code> is the path of the directory where WebIssues is installed
 +
* <code>WI_SCRIPT_URL</code> is the full URL of the entry script
 +
* <code>WI_BASE_URL</code> is the URL of WebIssues root directory
 +
 +
Always use <code>WI_ROOT_PATH</code> for loading or including files and use <code>WI_BASE_URL</code> 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 <code>System_Core_Application</code> and implement the <code>execute()</code> method. Then call the <code>bootstrap()</code> function with the name of the application class and an optional parameter which can have any meaning.

Latest revision as of 21:33, 26 June 2010

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.