Difference between revisions of "WebIssues/MVC Pattern"
(→Application) |
|||
(2 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 | + | 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. |
− | == | + | == Application == |
− | + | The WebIssues Server doesn't have a single entry point. The URLs point to individual scripts, for example: | |
− | www.example.com/ | + | 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 common parts and decorators also consist of a logic layer and a view layer, similar | ||
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 | ||
| | | | ||
− | +- | + | +- PageComponent |
| | | | ||
+- PageView | +- PageView | ||
Line 55: | Line 42: | ||
+- DecoratorComponent | +- DecoratorComponent | ||
| | | | | | ||
− | | +- | + | | +- DecoratorView |
| | | | ||
+- PartComponent | +- PartComponent | ||
| | | | ||
− | +- | + | +- PartView |
| | | | ||
+- SubPartComponent | +- SubPartComponent | ||
| | | | ||
− | +- | + | +- SubPartView |
− | A decorator acts just like a regular component, except that | + | 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
Contents
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.