SimpleT

SimpleT - An HTML Compiler

LICENSE
PHP License 3.0: http://www.php.net/license/3_0.txt

INTRODUCTION
SimpleT is about "reclaiming" PHP as a template language
for rendering HTML.

Another way to think about SimpleT is as a PHP output compiler...

A little history: PHP's original purpose was as a simple template language
for "binding" data, generated by C/C++ apps, to HTML. From those early days,
somehow PHP evolved into middleware, with the ability to connect to databases,
web services and loads more. This blurred PHP's original purpose as a web
template language, mixing application and presentation logic tiers normally
seperated by MVC like architectures.

Whether many PHP developers realised layering this issue, or where simply
looking for ways to remove HTML from PHP scripts to help web page designers
do their thing, a multitude of "template engines" came into being, the most
famous being Smarty (http://smarty.php.net).

What 99% of "template engines" do is define their own template syntax, often
evolving complete template languages and turning PHP into a interpreter for
parsing that language.

SimpleT takes the view that PHP already is an excellent
template language and when used purely for that purpose, is very easy for
web page designers to work with (in particular because there are a number
of tools popular design tools such as Dreamweaver with support for PHP syntax).

SimpleT takes advantage of PHP's output control functions to allow
the correct seperation of Model from View (or Application Logic from
Presentation Logic). Normally, developers who are using "pure" PHP for 
templates will do so by using functions like include() but, without output
buffering, this impacts the ability to control when output is sent to the 
browser.

SimpleT also uses PEAR::Cache_Lite to allow output to be cached
on the server side with an "expiry time", meaning exceptional performance
can be acheived. More on that below. For a tutorial on PEAR::Cache_Lite try
http://www.devshed.com/Server_Side/PHP/PHP_Cache_Lite/print_html

SimpleT also provides a lastModified() method to help with HTTP
client side caching using conditional GETs - see the http_cache_controller.php
example.

As SimpleT is not inventing any (template) programming languages,
templates are parsed by PHP itself, meaning it's also easier to debug,
profile, etc. etc.

The one thing that SimpleT can't do for you, which other template
engines that implement their own languages can, is restrict the templates
to a limited subset of PHP functionality. Perhaps PHP's safe mode will
eventually get the overhaul it needs to make that possible.

INSTALLATION NOTES
PHP needs read/write permissions to any directory that will be used to
store cached output. The examples use the subdirectory ./cache.

It's a good idea to have PHP error notices switched on;

error_reporting (E_ALL);

Or in php.ini so that if you use a variable in your template which you
forget to pass through from your model layer, PHP will throw a notice
to tell you about it.

USAGE
The examples *_controller.php should give a fairly clear
idea of using SimpleT. The SimpleT classes are documented
ready for PHPDocumentor (http://www.phpdoc.org) which will further
information.

++Backwards compatibility note (PHP < 4.3.0)
SimpleT requires PHP 4.3.0 because it uses extact() with the EXTR_REFS
switch (see http://www.php.net/extract) which was introduced in PHP
4.3.0. A patch for older PHP versions is to modify;
SimpleT_Binder::bind() to contain;

    function bind() {
        $__keys = array_keys($this->data);
        foreach ( $__keys as $_key) {
            $$_key = $this->data[$__key];
        }
        ob_start();
        include($this->viewHandler->location());
        $result = ob_get_contents();
        $this->viewHandler->cache($result);
        ob_end_clean();
    }

Note you will then not be able to use the variable $__key in your
templates.

++Caching
What's the fastest web page you know? One written in pure HTML perhaps?
SimpleT, as mentioned, is capable of caching output (e.g.
rendered HTML) on the server side.

[Note: here I'm talking about server side caching of rendered HTML -
NOT talking about client side browser caching with the help of HTTP
(see the http_cache_controller.php for an example of client side
caching) OR PHP Op code caching of the sort performed by PHP add ons
like Zend Accelerator.]

The idea behind the caching mechanism used by SimpleT is reduce the
amount of PHP executed, per page, to an absolute minimum. If you consider
that for a typical PHP based CMS, a given page will likely only change very
infrequently, you really only want to render that page from scratch, with PHP
once.

By default, when you "load" a template via the ViewHandler class, you
provide it with an expiry time in seconds. This should be regarded as
the maximum age you will let a cached version of the page "live" for.

When you called the isCached() method, if a cached version exists it's
content is loaded into the ViewHandler at that time. If no cache exists,
isCached() returns false allowing you to run the (perhaps lengthy) code
which performs SQL queries etc. etc.

SP_ViewHandler also provides the flush() method which can be used to delete
the cached version on demand. The flush_controller.php example gives a
very simple example of how this might be used.

The general strategy you could employ is to have code which "observes"
certain "events" in your application, events which could update the
HTML it renders. When one of those events happens, you could then flush()
the cache which applies to that page. For example, if you have a forum,
you might flush the cache when someone posts a new message.

Of course you won't always have a 1 to 1 relationship between a template
and the output that template displays.

A URL like "products.php?page=1" and "products.php?page=2" may use the
same template but the data they display could be very different.
SimpleT allows you to deal with this by using the forth, optional
"ID" argument to the ViewHandler's constructor. The flush2_controller.php
should give you an idea of how this works.

Note also SP_ViewHandler takes a fifth optional argument which is a "group"
which the cache file is stored under. Groups are part of PEAR::Cache_Lite,
allowing you to use the Cache_Lite::clean($groupName) method to flush all
cached data from a particular group. This can help also if you want to 
flush the cache of all content under a particular group.

++ Code Design Note
From a design point of view, you may want to consider creating subclasses
of ViewHandler for given templates which hard code the values which
ViewHandler is constructed with, as well as having some other class
with a factory method to create the ViewHandler subclasses. This should help
simplify your code and avoid situations where you're repeating the code that
creates a view handler.

I.e. creating a $viewHandler object for a template header.php might be;

$header = TemplateFactory::create('header');

The created subclass being;

class Header extends ViewHandler {
    function Header ($cache) {
        parent::SP_ViewHandler($cache,'header.php',3600);
    }
}

Also it's worth investigating the so called "Observer Pattern" as a means
to watch the "events" which happen within your application, that result in
the content being updated. For example if you have a blog which allows you
from your admin interface to add blog entries as well as allowing visitors to
post comments against particular entries, implementing an "Observer" for the
classes which deal with those actions would allow you to flush() the cache
for the relevant pages of your blog: this means PHP is only "compiling"
HTML when there's truly a change in the site - the rest of the time it's
simply delivering a cached version to a visitor. Again it helps when implementing
this to have a factory for creating SP_ViewHandler objects

++How to Code Templates
Regarding the templates themselves (examples found in the ./views subdir),
self discipline is required to make sure you do not violate the principles
of layering an MVC application.

For PHP web apps, essentially there are five layers in the application to
be concerned with (N-Tier);

- Presentation Layer: web browser
- Presentation Logic Layer: PHP code which generates HTML (or otherwise)
  AKA the "View" and "Controller" in MVC terms
- Domain Logic Layer / Application Logic Layer: PHP code that deals with
  transforming data from below into a form ready for the layer above. AKA
  the "Model" in MVC terms
- Data Access Layer: PHP code dealing with fetching data (from a database
  or the file system for example).
- Data Layer: a database (e.g. MySQL), the filesystem etc.

The "golden rule" - a thought expirement to test whether you have placed the
correct logic in the template is to ask;

"What would be required to replace your current template, which is
(for example) HTML with another content type such as XHTML, WML
(Wireless Markup Language)?"

If the answer is something like;

"I just need need to create a new template containing the alternative
content type. No database queries etc. etc. need to be reproduced"

...then you've probably got things about right.

Of course it's not always as simple as that - a WML page may present
a very different user interface to an HTML page but the principle is
to keep code reproduction to an absolute minimum. Certainly you should
be able to render both an HTML and an XHTML page on top of logic which
fetches some rows from a database, without having to reproduce the code
which fetches the data from the database.

So when writing templates, it's worth asking at all times, "Does this
code deal purely with presentation logic or am I doing stuff here which
belongs in lower layers (e.g. performing database queries)?"

As far is web page designers are concerned, assuming they have no knowledge
of PHP they will need to know at least some basic PHP syntax (the horror!).
In fact this is no different to teaching some custom template syntax - all
you need to do is show them how to echo($variable_name) and use the basic
PHP control structures( http://www.php.net/manual/en/control-structures.php )
like if/else, while and foreach.
Note personally I prefer to have open_short_tags = Off but you may find it
alot easier to switch it on so designers can use syntax like
 rather than; .

Some other general notes on what should / shouldn't appear in a template;

Logic like;

if ( $_POST['submit'] ) {
    echo ( 'Form submitted' );
} else {
    echo ( 'Form not submitted' );
}

Should NOT be placed in a template but rather implemented in a controller
(see the form_controller.php example). That's not to say if/else shouldn't
be used in a template but should rather be restricted to tasks which relate
only to the rendering of output (such as rendering a table with alternating
row colors).

Regarding PHP's global variables like $_GET and $_SESSION, a simple rule of
thumb is only to access these via a controller script.

The bottom line is (IMO) none of these should turn up in your templates
but rather be "re-bound" to PHP variables used but the template. This makes it
easier to cope with changes in your use of the variables (e.g. $_GET['view']
being changed to $_GET['action']) plus encourages more careful "screening"
of the variables (e.g. filter out attempted XSS attacks, SQL Injections etc.)

Otherwise templates need not exclude the use of functions and classes,
depending on how skillful the person working with the template is, so long
as the functions and classes used are purely related on presentation logic.
For example a class that builds HTML tables could well be used solely within
a template. But should that class also have methods for querying a database,
those calls should not be made within the template (arguably the class is
badly designed anyway - a god class).

As to includes - on the one hand it will make your controller simpler if
you allow templates to include other templates but on the other, you lose
the ability to cache the included files seperately.

++Note on Unit Tests
The unit tests provided use the SimpleTest framework;
http://www.lastcraft.com/simple_test.php

Should you wish to re-run them, you need to extract SimpleTest somewhere
then modify ./tests/index.php to point at that location. The tests are
fairly trivial.

This documentation was generated by phpDocumentor v1.2.1