LightRail PHP Application Framework

Version 1.0.5

Chapters

  1. What is LightRail and why use it?
  2. Features
  3. Requirements
  4. Download LightRail
  5. Model View Controller (MVC)
  6. Before you code
  7. Installation
  8. Configuration
  9. Getting URLs to work
  10. Custom URL routes
  11. Models
  12. Controllers
  13. Views
  14. Extending LightRail classes
  15. The LightRail class
  16. Using Zend classes

What is LightRail and why use it?

LightRail is a light-weight web application framework for PHP. LightRail automates routine tasks and organizes your code base. It layers data, presentation and logic using the model/view/controller pattern (MVC) in a manner popularized by Ruby on Rails.

Why use LightRail?

LightRail is:

There are many other frameworks for PHP employing MVC or a similar pattern and nearly all of them have a larger featureset than LightRail. It may very well be that you are better served by using one of them.

Unfortunately when frameworks attempt to be too comprehensive they can start turning into a 100 pound Swiss army knife — featureful but hardly ideal. LightRail handles a number of significant and repetitive aspects of your project and then lets PHP, itself a natural web application language, handle the rest. This is what makes it light.

Fast

You will notice that LightRail is a lot faster than other PHP frameworks, which is nice because good performance translates into being able to handle more site traffic volume.

Simple

Another benefit of using a light-weight framework is that it does not require a long learning process. PHP already handles a lot of web programming tasks very well on its own so why re-invent the wheel?

Extensible

LightRail does not presume that you lack ideas of your own. The inheritance structure works with your add-ons and modifications.

Open Source

LightRail is open source. You can modify it to suit your own needs and there is no spyware hiding in it.

Environment-Friendly

This point was not entirely tongue in cheek. Hardware consumes power and creates disposal issues at the end of its life cycle. By getting more out of your hardware you reduce carbon emissions, toxic waste and operating costs.

Features

Some of LightRail's features include:

Requirements

LightRail 1.0.5 requires a server which supports PHP 5.1.4 or higher. If you want to use the integrated database features you will also need PDO support and a PDO compatible database.

While the documentation is Apache-centric in places, there is no reason LightRail will not work in other environments.

Download LightRail

lightrail-1.0.5.zip

Model View Controller (MVC)

LightRail employs a model, view, controller pattern (MVC) similar to Ruby on Rails. MVC is a form of layered or tiered application development. By separating development into layers applications become more manageable, especially as they expand.

LightRail has classes corresponding to each of these components named LightRail_Record, LightRail_View and LightRail_Controller.

Model

The model layer (LightRail_Record) handles object relational mapping (ORM). ORM converts database records to programming objects and vice-versa. If you don't need to interact with a database you don't need this feature.

View

The view is the presentation/interface layer, usually HTML, which is output to the user.

Controller

The controller is the principal logic layer which ties everything together.

As with most things in life, ASCII art will facilitate our understanding:

 +-------------+
 |    View     |
 +-------------+
        |
 +-------------+
 | Controller  |
 +-------------+
        |
 +-------------+
 |    Model    |
 +-------------+
        |
 +-------------+
 | (Database)  |                   <- now it all makes sense
 +-------------+

Before you code

Magic methods

LightRail classes make use of some of the magic methods available in PHP 5. If you want to define a magic method in one of your child classes you should ensure it does not interfere with LightRail. (unless that is what you are intending)

Example:

<?php

class DemoController extends LightRail_Controller
{
    public function __get($name)
    {
        if ($name == 'sharkLaser') {
            // This part is up to you
        } else {
            return parent::__get($name);
        }
    }
}

File name case

Character case is important in LightRail file and directory names. This is in contrast to the case flexibility in LightRail URLs.

Installation

The easiest way to install LightRail is to download a zipped copy and place the contents of the 'web' folder in your web directory.

Let's take a look at the contents.

The site router — 'index.php'

The file 'index.php' is the site router. It handles all incoming requests and contains the site configuration.

The directories

The included directories are:

The application directory — 'application'

'application' contains the files for the application: the controllers, views, models, etc. It should not be browseable since none of the files in it handle direct requests.

The included '.htaccess' file will instruct most Apache servers not to serve content from the application directory. It does not hurt to test that access is denied when you browse to it. If your server does not support .htaccess configuration, secure the directory using the correct procedure.

Alternately, you can place the application directory outside the web viewable directory tree and point to it in your configuration.

The principle subdirectories of the application directory are:

The library directory — 'lib'

'lib' contains the LightRail libraries and can optionally be used to contain other PHP libraries you like to use. Obviously, it should not be browseable.

The included '.htaccess' file will instruct most Apache servers not to serve content from the library directory. It does not hurt to test that access is denied when you browse to it. If your server does not support .htaccess configuration, secure the directory using the correct procedure.

Alternately, you can place the library directory outside the web viewable directory tree.

The public directory — 'public'

'public' is used to contain files which are to be served directly, e.g. things like images and CSS stylesheets. It should be browseable. You do not need to use this folder but if you do LightRail_View can simplify URL's to it.

Configuration

LightRail is run with the static method LightRail::run() and configuration options are passed to it in a key/value array. Both the configuration and run call are contained in the router 'index.php' by default. Here is one example:

index.php
<?php

// Load LightRail
require('lib/lightrail-1.0.5/lightrail.php');

/////////////////////////////////////////////////
// Configuration
$conf = array();

// Use TestController::indexAction() as the site index
$conf['index'] = 'test/index';
/////////////////////////////////////////////////

// Run LightRail
LightRail::run($conf);

Alternately, if you prefer not to have configuration and/or logic in your web accessible directories you can just use 'index.php' to refer to a bootstrap file outside the web accessible directories and put the contents of 'index.php' into it.

index.php
<?php

// Load the bootstrap which contains the configuration and run call
require('../bootstrap.php');

Configuration options

Most of LightRail's default settings will work for a standard set up without needing to be set explicitly.

However, if you have more than one controller you will need to set the 'index' and if you are using the integrated database features you will also need to set the 'pdo' configurations.

index string

If you have more than one controller you will need to set this value.

The controller and action method to use as the site index, specified in the form of an application URL. Because it is an application URL you may provide additional segments if desired.

Example: 'test/index' would invoke TestController::indexAction() as the site index.

Defaults to 'default/index'.

pdo.dsn string
If you use LightRail's integrated database support this is where you provide the DSN database connection argument for PDO.

(php.net PDO documentation)

pdo.username string
The database username for PDO if required
pdo.password string
The database password for PDO if required
urlPrefix string
Defaults to 'index.php?'. The base name of the router file as needed to form application URLs. If you are using URL rewriting to point requests to the router file set this value to an empty string ''.
applicationDirectory string
Defaults to the subdirectory 'application'. If using a different directory you will need to let LightRail know the directory path (not the URL) with this setting.
publicDirectory string
Defaults to the subdirectory 'public'. A public directory is used to serve direct content such as images and style sheets. If using a different subdirectory for this purpose you can set the relative directory path with this setting.
publicUrl string
Defaults to the URL of the publicDirectory setting. If you have a public directory URL which does not fit this scheme (e.g. http://public.example.com/) you can use this setting to specify the URL (not the directory path) and it will be used instead.
routes array
A key/value array to map custom application URLs to the /controller/action/arg pattern LightRail uses. Each array key represents an incoming application URL and the corresponding value represents the applied application URL desired.
pregRoutes array
A key/value array to map custom application URLs to LightRail's /controller/action/arg pattern using Perl compatible regular expressions. Each array key is a regular expression pattern used to match incoming application URLs and the corresponding value is the replacement string. These two arguments need to be supplied in the format used by preg_replace().
session boolean
LightRail initiates PHP sessions by default. If you do not want PHP sessions set this to the boolean value false (not the string 'false') or 0.
notFound string
By default LightRail will load a file not found error document for URLs which do not have a corresponding controller and action. If this value is set to 'redirect' LightRail will redirect bad URLs to the site 'index' instead.
errorDocument string
LightRail provides a functional error document by default. If you prefer to use a custom document use this setting to supply the file path.
404Document string
If you would like to use a custom document just for 404 file not found errors, enter the file path with this setting. LightRail defaults to the 'errorDocument'.
ssl string
By default LightRail accepts either encrypted or unencrypted connections. (as supported by the server) If this value is set to 'always' LightRail will redirect request URLs which do not start with 'https://'. If set to 'never' LightRail will redirect URLs which do not start with 'http://'.

Getting URLs to work

The router file

In LightRail all requests are handled by the file 'index.php' in the top level directory. All application URLs need to get directed to that file. Because it handles all application requests it is reffered to as the router file.

It is important that your server treats 'index.php' as the directory index. Most PHP enabled servers already do that but if yours does not you will need to set it. For Apache this can often be done with an .htaccess directive.

.htaccess
DirectoryIndex index.php

There are three ways application URLs can be directed to the router file:

  1. Calling the router file in the URL
  2. Using URL rewriting to direct requests to the router file
  3. Using the router file as a custom 404 error document

LightRail can automate the generation of application URLs to suit the method you choose.

Calling the router file in the URL

LightRail application URL arguments can be placed after the router file name and query string symbol: 'index.php?'. This is known as the 'urlPrefix' in LightRail's configuration.

The 'urlPrefix' configuration setting will let you override the default if you would like to use a different variation which your server supports.

You can name the router file something other than 'index.php' if you configure your server to recognize the new file name as the directory index.

Using URL rewriting to direct requests to the router

URL rewriting is a feature some servers support which enables you to direct requests to a file without naming it in the URL.

This enables shorter, neater URLs.

If you use URL rewriting set the 'urlPrefix' configuration to an empty string ''.

If you have an Apache server with mod_rewrite enabled and .htaccess configuration, placing the following in your .htaccess file will direct any requests which are not for files in the 'public' directory to 'index.php'

.htaccess
RewriteEngine on
# Rewrite all URLs not pointing to the public directory
RewriteRule !^public\/ index.php
# Rewrite all remaining URLs which do not have a corresponding file
RewriteCond %{REQUEST_FILENAME} !-s
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^.*$ index.php

Using the router file as a custom 404 error document

This approach is a "hack". URL rewriting is preferrable.

If your server does not support URL rewriting but does support the ability to assign custom 404 (file not found) error documents you can largely mimick URL rewriting with a little bit of hacking.

First you need need to assign the router file as the 404 error document.

If you have an Apache server with .htaccess configuration it will look something like this.

.htaccess
DirectoryIndex index.php
ErrorDocument 404 /index.php

The second line which begins with 'ErrorDocument' is the one where index.php gets assigned as the custom 404 document. You will notice that there is a '/' slash preceeding 'index.php'. This represents the relative server directory path to the file. If your site is being served from a relative server directory other than '/' you will need to use that instead.

.htaccess
# If your site is being served from the server directory '/sites/005'
DirectoryIndex index.php
ErrorDocument 404 /sites/005/index.php

If you are unsure you can place the following code temporarily at the top of 'index.php' and browse to it to view the full file path you will need for the 'ErrorDocument 404' line.

index.php
<?php echo $_SERVER['SCRIPT_NAME']; exit;

Next you need to set the 'urlPrefix' configuration to an empty string ''.

While this method works for linking, it does not work for accepting data posted from a form. If you need to accept input from forms there is a work around: use the router file name and query string symbol for form URLs. The following is an example for a view with a form in it.

application/views/Test/getInput.php
<-- A standard application link -->

<a href="<?php echo $this->appUrl('test/get-input'); ?>">Refresh</a>

<-- The form action will need to simulate the 'urlPrefix' needed for post data -->

<form action="<?php echo $this->appUrl('index.php?/test/get-input'); ?>" method="post">

    <-- etc. -->

</form>

Keep in mind that if you have application URLs which match subdirectories you will get errors. e.g. http://example.com/application/ would try to index the 'application' directory rather than loading ApplicationController. If you had such a controller you could rename the directory, for example by placing an underscore at the beginning of its name, and accounting for it in your configuration.

And just to make things a little more interesting, even if this is working to serve content as expected, the server will send error headers to the browser telling it that it has a 404 not found error. For the sake of correctness you may want to override those headers.

index.php
<?php

require('lib/lightrail-1.0.5/lightrail.php');

header('Status: 200 OK');
header('HTTP/1.1 200 OK');

LightRail::run();

URL segments

When parsing the incoming URL for arguments, instead of relying entirely on query strings LightRail uses URL segments separated by '/' slashes to create more readable URLs.

instead of this:

http://example.com/index.php?controller=catalog&action=view+details&item=sharklaser

this:

http://example.com/index.php?/catalog/view-details/sharklaser

or with URL rewriting, this:

http://example.com/catalog/view-details/sharklaser

The following would also work:

In LightRail application URLs the first segment is used to denote which controller class to load and the second segment is used to denote which action method of the controller to call.

If there are more than two URL segments the third and following segments will be passed as arguments to the selected action method of the selected controller.

Consider the following URL (used in conjunction with URL rewriting):

http://example.com/catalog/view-details/sharklaser

The first URL segment calls for the CatalogController class and the second segment calls for the viewDetailsAction method. The third segment is passed as an argument to the viewDetailsAction method.

application/controllers/CatalogController.php
<?php

class CatalogController extends LightRail_Controller
{
    public function viewDetailsAction($item=null)
    {
        // $item corresponds to the third URL segment
        if ($item == 'sharklaser') {
            // This part is up to you
        }
    }
}

LightRail_Controller also provides a urlSegment() method to access individual URL segments. (an idea from CodeIgniter)

application/controllers/CatalogController.php
<?php

class CatalogController extends LightRail_Controller
{
    public function viewDetailsAction()
    {
        $seg1 = $this->urlSegment(1); // returns 'catalog'
        $seg2 = $this->urlSegment(2); // returns 'view-details'
        $seg3 = $this->urlSegment(3); // returns 'sharklaser'
        $seg4 = $this->urlSegment(4); // returns null
    }
}

Adding a query string to a LightRail URL

If you have query string data you would like to append to a LightRail URL you do not need the '?' symbol to start it.

http://example.com/controller/action/arg1=yes&arg2=no

The same example without URL rewriting:

http://example.com/index.php?/controller/action/arg1=yes&arg2=no

Custom URL routes

Custom URL 'routes' are not the same thing as the 'router' file.

Normally LightRail takes the first URL segment to select the controller and the second URL segment to select the action method. However, if you wish to use application URL segments which differ from that pattern, custom routes allow you to do so.

Custom routes are set in your configuration to map an incoming application URL to the applied application URL. In other words, you can map a customized application URL to the 'controller/action/arg' pattern LightRail expects.

Direct routes

Direct routes allow custom routing with negligible performance overhead. The routes are supplied to the configuration as a key/value array with the array key representing the incoming application URL and the value representing the applied application URL.

index.php
<?php

require('lib/lightrail-1.0.5/lightrail.php');

$conf['routes']['news'] = 'Blog/index/news';

LightRail::run($conf);

In this example the URL

http://example.com/news

gets treated as

http://example.com/blog/index/news

This means it would invoke BlogController::indexAction() with the argument 'news'.

Regular expression routes

You can also use Perl compatible regular expressions to format custom application URLs. This is much more flexible although it incurs a little more performance overhead, particularly if there is a large number of such routes.

'pregRoutes' are set in your configuration similarly to conventional direct routes except that the array key represents the regular expression to match against and the value represents the replacement string.

These arguments are formatted in accordance with PHP's preg_replace function.

index.php
<?php

require('lib/lightrail-1.0.5/lightrail.php');

$conf['pregRoutes']['/^news\/([0-9]{4}\-[0-9]{2}\-[0-9]{2})/'] = 'Blog/index/news/\\1';

LightRail::run($conf);

This example would respond to a URL such as

http://example.com/news/1952-04-01

treating it as

http://example.com/blog/index/news/1952-04-01

As a result LightRail would call BlogController::indexAction() with 'news' supplied as the first argument and '1952-04-01' supplied as the second argument.

Models

LightRail's object relational mapping class is LightRail_Record. It follows a simplified active record pattern and makes it easy to read, write and update database records in an object oriented manner.

Each model corresponds to a database table which is used to contain the records. There are a couple of conventions necessary for model tables:

  1. Each table must have a numerical field named 'id' which is used as the primary key.

    You can either set the 'id' field to be auto-incrementing or else a sequence table will be used. Auto-incrementing is generally preferrable if your database supports it. Otherwise if PDO has CREATE permission LightRail may be able to create sequence tables automatically.

  2. Field names are expected to begin with an ASCII compatible alphabetical character (a-z). This enables LightRail_Record to create a class attribute (property) corresponding to each field.
  3. Naming

    Each model requires a class name which ends with 'Record'. So if you had a model for working with 'handy items' you might name it 'HandyItemRecord'. Model files are named after the class so the file would be 'application/models/HandyItemRecord.php'.

    Example of a model class:

    application/models/HandyItemRecord.php
    <?php
    
    class HandyItemRecord extends LightRail_Record
    {
        protected $_table = 'handy_item';
    
        public function validate()
        {
            $this->validateNotEmpty('name', 'price');
            $this->validateNumeric('quantity', 'price');
        }
    }
    

    And now an example of the model being used in a controller:

    $item = new HandyItemRecord();
    
    $item->name = 'My Item';
    $item->price = 1.00; // wink
    
    if ($item->save()) {
        $_SESSION['notice'] = 'The handy item has been saved';
        $this->redirectTo('handy-item/index'); // This is a controller method
    }
    

    Some things to note in the preceeding example:

    LightRail_Record has the following attributes which may be set explcitly:

    _table string
    The name of the database table for the model. Defaults to the uncamelized name of the model without the trailing 'Record'. e.g. Class 'HandyItemRecord' would look for the table 'handy_item'
    _sequenceTable string
    This field is unnecessary if you have an auto-incrementing 'id' field. Otherwise if you want a specific name for a sequence table to track the 'id' field you can set it here. Defaults to the table name with '_seq' appended.
    _fields array
    With most databases LightRail can determine the database table field names automatically but if needed you can explicitly set them here in an array.
    _serializedFields array
    LightRail_Record can automatically store and retrieve serialized PHP for designated table fields if you set their names in this array.
    _errors array
    A key/value array to note errors for validation. LightRail_Record validation methods automatically make use of it but you may also set error notices directly by using the affected field as the key and the error message as the value.

    You will notice that all of the aformentioned properties begin with '_', the underscore character. For compatibility with LightRail_Record, your database field names should begin with an alphabetical character.

    LightRail_Record enables you to define the following methods which will get called automatically:

    init()
    Called when the model object is constructed
    beforeSave()
    Called before a record is saved to the database and before 'validate'
    validate()
    Called before a record is saved to the database
    beforeCreate()
    Called before a new record is created and before 'validateOnCreate'
    validateOnCreate()
    Called before a new record is created
    afterCreate()
    Called after a new record is successfully created
    beforeUpdate()
    Called before an existing record is updated and before 'validateOnUpdate'
    validateOnUpdate()
    Called before an existing record is updated
    afterUpdate()
    Called after an existing record is successfully updated
    beforeDelete()
    Called before a record is deleted
    afterDelete()
    Called after a record is successfully deleted

    LightRail_Record has the following predefined methods:

    find(mixed $sqlOrRecordId [, array $bindValues])
    Returns an array of records which match the provided arguments. The first argument can be one of: a full SELECT query, a WHERE clause, or just a numerical id for a specific record. The optional second argument accepts PDO bind values.
    load(mixed $sqlOrRecordId [, array $bindValues])
    Sets the attributes of the current model object to match the first record which matches the provided arguments. The first argument can be one of: a full SELECT query, a WHERE clause, or just a numerical id for a specific record. The optional second argument accepts PDO bind values.
    setAttributes(array $inputArray)
    Sets the field attributes of the model object to match the supplied key/value array
    save()
    Saves the model object as a database record if it passes validation
    delete()
    Deletes the model object record from the database

    The constructor can also be passed arguments for the 'load()' method to instantiate the model and load a matching record at the same time.

    // Return the model object
    // Also load it with the database record whose 'id' is 12 (if it exists)
    $item = new HandyItemRecord(12);
    

    LightRail_Record currently includes the following validation methods:

    validateNotEmpty(string $field1 [, $field2 (etc)])
    Validate that the specified fields are not empty
    validateNumeric(string $field1 [, $field2 (etc)])
    Validate that the specified fields are assigned numeric values
    validateFormat(string $field, string $regExp)
    Validate that the supplied string matches the supplied regular expression
    validateEmail(string $field1 [, $field2 (etc)])
    Validate that the specified fields are in the format of a standard email address
    validateUnique(string $field1 [, $field2 (etc)])
    Validate that the specified fields have a unique value for the database table

    If more than one field is passed to this method the combination of those fields is checked for uniqueness.

    LightRail_Record can save a lot of repetitive coding but it also incurs more performance overhead than using PDO directly. You have the option of accessing PDO directly through the static method LightRail::pdo().

    // Grab a list of handy items which are in stock
    
    $db = LightRail::pdo();
    $sql = 'SELECT * FROM handy_item WHERE quantity > 0 ORDER BY name';
    $items = $db->query($sql)->findAll();
    

    Controllers

    The controller is the principal logic layer which ties the other layers together.

    Naming

    Each controller requires a class name which ends with 'Controller'. Often a controller is named after a model it works with, so if you had a 'HandyItem' controller for working with the model 'HandyItemRecord' you would name the class 'HandyItemController'. However, the controller would be referred to in configuration, URLs and elsewhere simply as 'HandyItem'.

    Controller files are named after the class so the file name for this example would be application/controllers/HandyItemController.php

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function indexAction()
        {
            // Get an array of all stocked 'handy items' and make it available to the view
            $model = new HandyItemRecord();
            $this->view->items = $model->find('quantity > 0');
        }
    }
    

    Some things to note from the preceding example:

    As mentioned in the chapter on LightRail URLs, the first two application URL segments select the controller and action.

    The following URL invokes HandyItemController::indexAction()

    http://example.com/handy-item/index

    These would also work:

    If you have logic to run before the action method you can define the public method init() which will be called before the action.

    application/controllers/HandyItemController.php
    class HandyItemController extends LightRail_Controller
    {
        public function init()
        {
            // Set the layout template to use with this controller
            $this->setLayout('handyItemLayout.phtml');
        }
    
        public function indexAction()
        {
            // Get an array of all stocked 'handy items' and make it available to the view
            $model = new HandyItemRecord();
            $this->view->items = $model->find('quantity > 0');
        }
    }
    

    If you have logic or methods to make available to all controllers (or a group of controllers) you can place them in a parent class which extends LightRail_Controller and have the action controller extend your custom class.

    application/controllers/MyController.php
    <?php
    
    class MyController extends LightRail_Controller
    {
        // Logic to execute before any action method
        public function init()
        {
            $this->setLayout('site.phtml');
        }
    
        // Method available to child classes
        protected function _avoidClothingLecture($clothing)
        {
            if ($clothing->isUgly) {
                $clothing->hideFromSpouse();
            }
        }
    }
    
    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends MyController
    {
        public function init()
        {
            // Remember to call the parent class init method
            parent::init();
    
            $model = new HandyItemRecord();
            $items = $model->find('quantity > 0 ORDER BY name');
    
            foreach ($items as $item) {
                if ($item->type == 'clothing') $this->_avoidClothingLecture($item);
            }
        }
    }
    

    Some things to note from this example:

    LightRail_Controller uses the magic method __call() to return a 404 file not found error when a URL requests an action which has no corresponding definition. If you want to override this method you may want to ensure there is still some manner of 404 error handling.

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function __call($method, $args)
        {
            if ($method == 'deployLaserShark') {
                // this part is up to you
            } else {
                return parent::__call($method, $args);
            }
        }
    }
    

    LightRail_Controller has the following methods:

    setView(mixed $pathOrNull)
    Sets a specific file for the view or sets the view to null
    getView()
    Returns the view setting
    setLayout(mixed $pathOrNull)
    Sets a file for the layout or sets the layout to null
    getLayout()
    Returns the layout setting
    setContentForLayout(string $html)
    Directly sets the content for the view instead of using a view file
    getContentForLayout()
    Returns the view content for use in a layout
    urlSegment(integer $segmentNumber)
    Returns the value of the URL segment or null if it does not exist
    redirectTo(string $url)
    Redirects the browser to the supplied URL. If an application URL is desired, only the segment URL needs to be supplied. e.g. $this->redirectTo('handy-item/index');

    Views

    The view is the presentation/interface layer which is output to the browser. (or other consumer) Normally this is HTML but the view can also be used to output other text or binary content such as images.

    Naming

    Each view is associated with a controller and action. In order for LightRail to automatically find and use a given view it should be named after the action and reside in a directory named after the controller.

    View files can end in the extension '.phtml' or '.php'. Some developers prefer to use the '.phtml' extension to differentiate presentation related files from application logic related files.

    So the view file for HandyItemController::indexAction() would be:

    application/views/HandyItem/index.phtml

    Language

    LightRail uses PHP for the view as it is compatible with presentational markup and avoids the inherent performance penalty of custom template languages. This does mean it is possible to include logic which is unrelated to presentation but that decision is left up to the developer.

    Content

    The view contains the HTML content which is output to the browser.

    Let us revisit HandyItemController::indexAction()

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function indexAction()
        {
            // Get a list of handy items which are in stock
            $model = new HandyItemRecord();
            $this->view->items = $model->find('quantity > 0 ORDER BY name');
        }
    }
    

    Now the corresponding view:

    application/views/HandyItem/index.phtml
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
        <head>
            <title>Handy Items</title>
            <link rel="stylesheet" type="text/css" href="<?php echo $this->publicUrl('css/styles.css'); ?>" />
        </head>
        <body>
            <h1>Handy Items</h1>
    
            <ol>
    
                <?php foreach ($this->items as $item): ?>
    
                <li><?php echo $this->escape($item->name); ?></li>
    
                <?php endforeach; ?>
    
            </ol>
    
        </body>
    </html>
    

    A few things to note about the preceding example:

    Specifying an alternate view

    A view other than the action default can be selected using the setView() controller method. This can be useful if the same view file is used by more than one action method.

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function indexAction()
        {
            $model = new HandyItemRecord();
            $this->view->items = $model->find();
        }
    
        /**
         * This action is exactly the same as 'index'
         * except that it only indexes large items
         */
        public function largeItemIndexAction()
        {
            // Get a list of large handy items
            $model = new HandyItemRecord();
            $this->view->items = $model->find('size=?', array('large'));
    
            // The 'index' view will do the job
            $this->setView('index');
        }
    }
    

    Using setContentForLayout() instead of a view file

    View content can be set directly by using the controller method setContentForLayout() instead of a view file. (This method works even if you are not using a layout.)

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function indexAction()
        {
            // Get a list of handy items which are in stock
            $model = new HandyItemRecord();
            $items = $model->find('quantity > 0 ORDER BY name');
    
            $html = "<h1>Handy Items</h1>\n"
                  . "<ol><li>"
                  . implode("</li><li>", $items)
                  . "</li></ol>\n";
    
            $this->setContentForLayout($html); // no view file is used
        }
    }
    

    If a layout is being used LightRail will leave it up to the layout to grab the content using the view method getContentForLayout(). Otherwise LightRail will render the content automatically.

    Layouts

    LightRail supports basic layouts. A layout supplies a page template which any number of views can be pulled into. Layouts reside in the 'application/views/layouts/' directory. Example:

    application/views/layouts/default.phtml
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
        <head>
            <title><?php echo $this->title; ?></title>
            <link rel="stylesheet" type="text/css" href="<?php echo $this->publicUrl('css/styles.css'); ?>" />
        </head>
        <body>
    
            <div id="siteNavigation">
                <ul>
                    <li><a href="<?php echo $this->appUrl('handy-item/index'); ?>">Handy Items</a></li>
                    <li><a href="<?php echo $this->appUrl('handy-item/recommend'); ?>">Recommend an Item</a></li>
                </ul>
            </div>
    
            <div id="pageContent"><?php echo $this->getContentForLayout(); ?></div>
    
            <div id="copyright">Copyright &copy; 1952 Handy Example Co.</div>
    
        </body>
    </html>
    

    Because the layout supplies the larger document structure the view can limit itself to the content requested by the getContentForLayout() method. Example:

    application/views/HandyItem/index.phtml
    <h1>Handy Items</h1>
    
    <ol>
    
        <?php foreach ($this->items as $item): ?>
    
        <li><?php echo $this->escape($item->name); ?></li>
    
        <?php endforeach; ?>
    
    </ol>
    

    A few things to note about the preceding example:

    The controller method setLayout() can be used to assign a layout to a controller or individual action as well. setLayout() can also be called with the argument null to instruct LightRail not to use a layout.

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function init()
        {
            // Use the layout 'handyItem.phtml' for this controller
            $this->setLayout('handyItem');
        }
    
        // etc.
    
        // The view for this particular action does not use a layout
        public function layoutLessAction()
        {
            $this->setLayout(null);
        }
    }
    

    The following methods are available in LightRail_View:

    escape(string $html)
    Escapes HTML characters using htmlentities()
    appUrl(string $urlSegments)
    Returns a full URL for the application segments/query string provided
    publicUrl(string $localUrl)
    Returns a full URL to the public directory plus the string which was entered
    url(string $url)
    Invokes appUrl() if a partial URL is entered or returns the string if a full URL is entered
    monthName(integer $monthNumber [,integer $nameLength])
    Returns the English month corresponding to the month number provided. If a name length is specified the returned value will not exceed that number of characters.
    linkButton(string $text, string $url)
    Returns a javascript form button link to the supplied application or full URL. Also returns a conventional link for clients which do not have Javascript.

    Helpers

    View helpers are not currently supported but they may be supported soon in a manner similar to Zend Framework.

    In the mean time LightRail_View can be extended with a custom class if desired.

    Extending LightRail classes

    You can always create your own intermediate custom classes to add functionality to the LightRail classes.

    If you want your custom class to autoload you can register it with the static method LightRail::autoloadClass($class, $file). This can either be done before calling LightRail::run() or in the controller's init() method.

    If you create an intermediate class for LightRail_View you can use it by explicitly setting the 'view' property of the controller in its init() method.

    application/controllers/HandyItemController.php
    <?php
    
    class HandyItemController extends LightRail_Controller
    {
        public function init()
        {
            // Use a customized class for the view
            $this->view = new MyView();
        }
    }
    

    The LightRail class

    The different classes in LightRail need to share a number of variables and functions between them. To provide global functionality without taking up global namespace the LightRail class contains global static methods both for the framework's own internal use and for users who may want to leverage them directly.

    While some of these methods are designed for LightRail's internal use there are a number of methods which might have a use in your router, controllers or models.

    LightRail::run(array $configuration)
    Runs the LightRail framework. This method needs to be called in your router ('index.php').
    LightRail::pdo()
    Returns the PHP PDO object for your database. If it has not been instantiated yet LightRail will attempt to connect to the database. You will need to have supplied LightRail::run() with the connection details.
    LightRail::camelize(string $string [, string $separators='_-])
    Returns a camel cased version of the input string.
    LightRail::uncamelize(string $string [, string $separator='_'])
    Returns a lower cased version of the input string converted from camel casing to separators. The default separator is an underscore.
    LightRail::lcfirst(string $string)
    Returns the string with the first character converted to lower case.
    LightRail::errorPage(string $heading, string $paragraph1 [, string $paragraph2 (etc)])
    Outputs an error page
    LightRail::error404()
    Outputs a 404 file not found error page
    LightRail::includePath(string $directoryPath)
    Adds the supplied directory path to PHP's include_path setting
    LightRail::autoloadClass(string $class, string $definitionPath)
    LightRail will make the supplied class definition available to __autoload().
    LightRail::autoload(string $class)
    WARNING: THIS METHOD IS NOT FOR STANDARD USE. DO NOT USE THIS METHOD UNLESS YOU NEED AND UNDERSTAND IT. LightRail makes extensive use of class autoloading and by default this method gets mapped to __autoload() to ensure that happens in the expected fashion. However, if you have a need for your own autoloading patterns and the provided LightRail::autoloadClass() method is not adequate, you may define PHP's __autoload() global function before including 'lightrail.php' but be sure to invoke LightRail::autoload() for cases which your custom patterns do not match. Otherwise you will need to replicate the functionality of LightRail::autoload() in your __autoload() definition.

    Using Zend classes

    Many Zend framework classes are not dependent on Zend framework and may be useful to a LightRail project. (e.g. Zend_Pdf)

    LightRail can autoload Zend classes if you add the top level Zend class directory to PHP's include_path setting. One way this can be done conveniently is with the static method LightRail::includePath($path), either calling it in the router file before LightRail::run() or in a specific controller's init() method.