Cached FastTemplate 1.1.0 - PHP extension for managing templates, and performing variable interpolation.


NAME

Cached FastTemplate 1.1.0 - PHP extension for mamanging templates and performing variable interpolation.


SYNOPSIS

    include("class.FastTemplate.php3");
    $tpl = FastTemplate("/path/to/templates");
    $tpl->define( array(    main    => "main.tpl",
                            row     => "table_row.tpl",
                            all     => "table_all.tpl"  ));

    $tpl->assign(TITLE, "I am the title.");
    $defaults = ( array(    FONT    => '<font size=+2 face=helvetica>',
                            EMAIL   => 'cdi@thewebmasters.net' ));

    $tpl->assign($defaults);
    $tpl->parse(ROWS, ".row");    // the '.' appends to ROWS
    $tpl->parse(CONTENT, array("row", "all"));
    $tpl->parse(CONTENT, "main");
    $tpl->FastPrint(CONTENT);

    $raw = $tpl->fetch("CONTENT");
    echo "$raw\n";

But see below for more complex examples.


DESCRIPTION

What is a template?

A template is a text file with variables in it. When a template is parsed, the variables are interpolated to text. (The text can be a few bytes or a few hundred kilobytes.) Here is a simple template with one variable ('{NAME}'):

    Hello {NAME}.  How are you?

When are templates useful?

Templates are very useful for CGI programming, because adding HTML to your PHP code clutters your code and forces you to do any HTML modifications. By putting all of your HTML in seperate template files, you can let a graphic or interface designer change the look of your application without having to bug you, or let them muck around in your PHP code.

Why use FastTemplate?

Speed

FastTemplate parses with a single regular expression. It just does simple variable interpolation (i.e. there is no logic that you can add to templates - you keep the logic in the code). That's why it's has 'Fast' in it's name!

Flexibility

The API is robust and flexible, and allows you to build very complex HTML documents/interfaces. It is also completely written in PHP and (should) work on Unix or NT. Also, it isn't restricted to building HTML documents -- it could be used to build any ascii based document (postscript, XML, email - anything).

What are the steps to use FastTemplate?

The main steps are:

    1. define
    1a.cache
    2. assign 
    3. parse
    3a.cache write
    4. FastPrint

These are outlined in detail in CORE METHODS below.


CORE METHODS


define( array( key,value pairs) )

The method define() maps a template filename to a (usually shorter) name;

    $tpl = new FastTemplate("/path/to/templates");

    $tpl->define( array(    main    => "main.tpl",
                            footer  => "footer.tpl" ));

This new name is the name that you will use to refer to the templates. Filenames should not appear in any place other than a define().

(Note: This is a required step! This may seem like an annoying extra step when you are dealing with a trivial example like the one above, but when you are dealing with dozens of templates, it is very handy to refer to templates with names that are indepandant of filenames.)

TIP: Since define() does not actually load the templates, it is faster and more legible to define all the templates with one call to define().


define_nofile() alias: define_raw()

THESE METHODS ARE NOT PORTED TO THE PHP VERSION

And probably never will be. The purpose of this class is to eliminate HTML from your PHP code, not to create new ways of adding it back in.


define_dynamic($Macro, $ParentName)

Nino Martincevic, don@agi.de, emailed me with a question about doing something like this, and I thought it was a such a cool idea I immediately sat down and cranked it out...

You can define dynamic content within a static template. (Lists) Here's an example of define_dynamic();

    $tpl = new FastTemplate("./templates");

    $tpl->define(    array( main  =>  "main.tpl",
                            table =>  "dynamic.tpl" ));

    $tpl->define_dynamic( "row" , "table" );

This tells FastTemplate that buried in the ``table'' template is a dynamic block, named ``row''. In older verions of FastTemplate (pre 0.7) this ``row'' template would have been defined as it's own file. Here's how a dynamic block appears within a template file;

    <!-- NAME: dynamic.tpl -->
    <table>

    <!-- BEGIN DYNAMIC BLOCK: row -->
    <tr>
    <td>{NUMBER}</td>
    <td>{BIG_NUMBER}</td>
    </tr>
    <!-- END DYNAMIC BLOCK: row -->

    </table>
    <!-- END: dynamic.tpl -->

The syntax of your BEGIN and END lines needs to be VERY exact. It is case sensitive. The code block begins on a new line all by itself. There cannot be ANY OTHER TEXT on the line with the BEGIN or END statement. (although you can have any amount of whitespace before or after) It must be in the format shown;

    <!-- BEGIN DYNAMIC BLOCK: handle_name -->

The line must be exact, right down to the spacing of the characters. The same is true for your END line. The BEGIN and END lines cannot span multiple lines. Now when you call the parse() method, FastTemplate will automatically spot the dynamic block, strip it out, and use it exactly as if you had defined it as a stand-alone template. No additional work is required on your part to make it work - just define it, and FastTemplate will do the rest. Included with this archive should have been a file named define_dynamic.phtml which shows a working example of a dynamic block.

There are a few rules when using dynamic blocks - dynamic blocks should not be nested inside other dynamic blocks - strange things WILL occur. You -can- have more than one nested block of code in a page, but of course, no two blocks can share the same defined handle. The error checking for define_dynamic() is miniscule at best. If you define a dynamic block and FastTemplate fails to find it, no errors will be generated, just really weird output. (FastTemplate will not append the dynamic data to the retured output) Since the BEGIN and END lines are stripped out of the parsed results, if you ever see your BEGIN or END line in the parsed output, that means that FastTemplate failed to find that dynamic block.


clear_dynamic($Macro)

This provides a method to remove the dynamic block definition from the parent macro provided that you haven't already parsed the template. Using our example above:

    $tpl->clear_dynamic("row");

Would completely strip all of the unparsed dynamic blocks named ``row'' from the parent template. This method won't do a thing if the template has already been parsed! (Because the required BEGIN and END lines have been removed through the parsing) This method works well when you are accessing a database, and your ``rows'' may or may not return anything to print to the template. If your database query doesn't return anything, you can now strip out the rows you've set up for the results. (Gee, maybe I ran into this problem myself ? :-)


assign( (key,value pair) or ( array(key value pairs) )

The method assign() assigns values for variables. In order for a variable in a template to be interpolated it must be assigned. There are two forms which have some important differences. The simple form, is to accept an array and copy all the key/value pairs into an array in FastTemplate. There is only one array in FastTemplate, so assigning a value for the same key will overwrite that key.

    $tpl->assign(TITLE    => "king kong");
    $tpl->assign(TITLE    => "godzilla");    // overwrites "king kong"


parse(RETURN, FileHandle(s) )

The parse function is the main function in FastTemplate. It accepts a new key value pair where the key is the TARGET and the values are the SOURCE templates. There are three forms this can be in:

    $tpl->parse(MAIN, "main");                     // regular
    $tpl->parse(MAIN, array ( "table", "main") );  // compound
    $tpl->parse(MAIN, ".row");                     // append

In the regular version, the template named ``main'' is loaded if it hasn't been already, all the variables are interpolated, and the result is then stored in FastTemplate as the value MAIN. If the variable '{MAIN}' shows up in a later template, it will be interpolated to be the value of the parsed ``main'' template. This allows you to easily nest templates, which brings us to the compound style.

The compound style is designed to make it easier to nest templates. The following are equivalent:

    $tpl->parse(MAIN, "table");
    $tpl->parse(MAIN, ".main");

    // is the same as:

    $tpl->parse(MAIN, array("table", "main"));
    // this form saves function calls and makes your code cleaner

It is important to note that when you are using the compound form, each template after the first, must contain the variable that you are parsing the results into. In the above example, 'main' must contain the variable '{MAIN}', as that is where the parsed results of 'table' is stored. If 'main' does not contain the variable '{MAIN}' then the parsed results of 'table' will be lost.

The append style allows you to append the parsed results to the target variable. Placing a leading dot . before a defined file handle tells FastTemplate to append the parsed results of this template to the returned results. This is most useful when building tables that have an dynamic number of rows - such as data from a database query.


strict()

When strict() is on (it is on by default) all variables found during template parsing that are unresolved have a warning printed to STDERR;

[FastTemplate] Warning: no value found for variable: SOME_VAR

Also, the variables will be left in the output document. This was done for two reasons: to allow for parsing to be done in stages (i.e. multiple passes), and to make it easier to identify undefined variables since they appear in the parsed output. If you want to replace unknown variables with an empty string, see: no_strict().

Note: STDERR output should be captured and logged by the webserver. With apache (and unix!) you can tail the error log during development to see the results as in;

        tail -f /var/log/httpd/error_log


no_strict()

Turns off warning messages about unresolved template variables. A call to no_strict() is required to replace unknown variables with an empty string. By default, all instances of FastTemplate behave as is strict() was called. Also, no_strict() must be set for each instance of FastTemplate;

    $tpl = new FastTemplate("/path/to/templates");
    $tpl->no_strict();


cache_expire(FileHandle, seconds )

The cache_expire method sets the time of life for a cached region. The age is specified in seconds. For example, if the cache was written to at 11:51:42, and the expire is set to 20 seconds, the cache will be invalidated at 11:52:02.

This value if only useful during cache reads, not cache writes. This means that you can not set global cache policy for a region, but does allow you to easily change times when the cache will expire.

If neither cache_expire nor cache_refresh are called, FastTemplate will use a cache_expire of 600 seconds.

It is possible to call both cache_expire and cache_refresh for the same read. In this case, if either condition is true, the cache will be invalidated.

    $tpl = new FastTemplate("/path/to/templates");
    $tpl->define(array(main=>"template.tpl"));
    $tpl->cache_expire(main, 20);   // This will invalidate the cache after 20 seconds.


cache_refresh(FileHandle, frequency )

The cache_refresh method sets the frequency by which a cached region is invalidated. The frequency can be one of minute, quarterHour, halfHour, hour, halfDay, day, or month. For example, if you are refreshing every minute, and the cache is written at 11:51:42, the cache will be invalidated at 11:52:00.

This value if only useful during cache reads, not cache writes. This means that you can not set global cache policy for a region, but does allow you to easily change times when the cache will expire.

If neither cache_expire nor cache_refresh are called, FastTemplate will use a cache_expire of 600 seconds.

It is possible to call both cache_expire and cache_refresh for the same read. In this case, if either condition is true, the cache will be invalidated.

    $tpl = new FastTemplate("/path/to/templates");
    $tpl->define(array(main=>"template.tpl"));
    $tpl->cache_refresh(main, "hour");   // This will invalidate the cache on the hour..


is_cached(RETURN, FileHandle )

This method returns TRUE if the region has a valid cache. (See cache_expire and cache_refresh for information about cache invalidation.) If a valid cache file is not available, this method will return FALSE.

As a side effect, when this function returns TRUE, it acts as if parse had been called. In other words, this function will load the cached file into memory and insert it into RETURN.

    $tpl = new FastTemplate("/path/to/templates");
    $tpl->define(array(main=>"template.tpl"));
    $tpl->cache_refresh(main, "hour");   // This will invalidate the cache on the hour.

    if (!$tpl->is_cached (MAIN, main)) {
      $body["title"] = "Cache test";
      $tpl->assign($body);
      $tpl->parse(MAIN, main);
      $tpl->write_cache (MAIN, "main");
    }
    $tpl->FastPrint();


write_cache(RETURN, FileHandle )

This method will write the cache file with the generated page.

    $tpl = new FastTemplate("/path/to/templates");
    $tpl->define(array(main=>"template.tpl"));
    $tpl->cache_refresh(main, "hour");   // This will invalidate the cache on the hour.

    if (!$tpl->is_cached (MAIN, main)) {
      $body["title"] = "Cache test";
      $tpl->assign($body);
      $tpl->parse(MAIN, main);
      $tpl->write_cache (MAIN, "main");
    }
    $tpl->FastPrint();


FastPrint(HANDLE)

The method FastPrint() prints the contents of the named variable. If no variable is given, then it prints the last variable that was used in a call to parse() which I find is a reasonable default.

    $tpl->FastPrint();       // continuing from the last example, would
                             // print the value of MAIN

    $tpl->FastPrint("MAIN"); // ditto

This method is provided for convenience. If you need to print somewhere else (a socket, file handle) you would want to fetch() a reference to the data first:

    $data = $tpl->fetch("MAIN");
    fwrite($fd, $data);     // save to a file


OTHER METHODS


fetch(HANDLE)

Returns the raw data from a parsed handle.

    $tpl->parse(CONTENT, "main");
    $content = $tpl->fetch("CONTENT");
    print $content;        // print to STDOUT
    fwrite($fd, $content); // write to filehandle


get_assigned($Var) Christian Brandel cbrandel@gmx.de

This method will return the value of a variable that has been set via assign(). This allows you to easily pass variables around within functions by using the FastTemplate class to handle ``globalization'' of the variables. For example;

    $tpl->assign(  array(  TITLE    =>    $title,
                           BGCOLOR  =>    $bgColor,
                           TEXT     =>    $textColor ));

    (sometime later...)
    $bgColor = $tpl->get_assigned(BGCOLOR);


clear()

Note: All of the clear() functions are for use anywhere where your scripts are persistant. They generally aren't needed if you are writing CGI scripts.

clear() Clears the internal references that store data passed to parse(). clear() accepts individual references, or array references as arguments.

Often clear() is at the end of a script:

    $tpl->FastPrint("MAIN");
    $tpl->clear("MAIN");

    or

    $tpl->FastPrint("MAIN");
    $tpl->FastPrint("CONTENT");
    $tpl->clear(array("MAIN","CONTENT"));

If called with no arguments, removes ALL references that have been set via parse().


clear_parse()

See: clear()


clear_href(KEY)

Removes a given reference from the list of refs that is built using:

    $tpl->assign(KEY = val);

If called with no arguments, it removes all references from the array.

(Same as clear_assign() )

    $tpl->assign(    array(    MOVIE  =>  "The Avengers",
                               RATE   =>  "Sucked"    ));

    $tpl->clear_href("MOVIE");
    // Now only {RATE} exists in the assign() array


clear_define()

Clears the internal list that stores data passed to:

    $tpl->define();

Note: The hash that holds the loaded templates is not touched with this method. ( See: clear_tpl() ) Accepts a single file handle, an array of file handles, or nothing as arguments. If no argument is given, it clears ALL file handles.

    $tpl->define( array( MAIN => "main.tpl",
                         BODY => "body.tpl",
                         FOOT => "foot.tpl"  ));

    // some code here

    $tpl->clear_define("MAIN");


clear_tpl()

Clears the internal array that stores the contents of the templates. (If they have been loaded) If you are having problems with template changes not being reflected, try adding this method to your script.

    $tpl->define(MAIN,"main.tpl" );
    // assign(), parse() etc etc...

    $tpl->clear_tpl(MAIN);    // Loaded template now unloaded.


clear_all()

Cleans the module of any data, except for the ROOT directory. Equivalent to:

    $tpl->clear_define();
    $tpl->clear_href();
    $tpl->clear_tpl();
    $tpl->clear_parse();

In fact, that's exactly what it does.


Variables

A variable is defined as:

    {([A-Z0-9_]+)}

This means, that a variable must begin with a curly brace '{'. The second and remaining characters must be uppercase letters or digits 'A-Z0-9'. Remaining characters can include an underscore. The variable is terminated by a closing curly brace '}'.

For example, the following are valid variables:

    {FOO}
    {F123F}
    {TOP_OF_PAGE}


Variable Interpolation (Template Parsing)

If a variable cannot be resolved to anything, a warning is printed to STDERR. See strict() and no_strict() for more info.

Some examples will make this clearer.

    Assume:

    $FOO = "foo";
    $BAR = "bar";
    $ONE = "1";
    $TWO = "2";    
    $UND = "_";
    
    Variable    Interpolated/Parsed
    ------------------------------------------------
    {FOO}            foo    
    {FOO}-{BAR}      foo-bar
    {ONE_TWO}        {ONE_TWO} // {ONE_TWO} is undefined!    
    {ONE}{UND}{TWO}  1_2
    ${FOO}           $foo
    $25,000          $25,000
    {foo}            {foo}     // Ignored, it's not valid, nor will it
                               // generate any error messages.


FULL EXAMPLE

This example will build an HTML page that will consist of a table. The table will have 3 numbered rows. The first step is to decide what templates we need. In order to make it easy for the table to change to a different number of rows, we will have a template for the rows of the table, another for the table, and a third for the head/body part of the HTML page.

Below are the templates. (Pretend each one is in a separate file.)

  <!-- NAME: main.tpl -->
  <html>
  <head><title>{TITLE}</title>
  </head>
  <body>
  {MAIN}
  </body>
  </html>
  <!-- END: main.tpl -->
 
 
  <!-- NAME: table.tpl -->
  <table>
  {ROWS}
  </table>
  <!-- END: table.tpl -->
 
 
  <!-- NAME: row.tpl -->
  <tr>
  <td>{NUMBER}</td>
  <td>{BIG_NUMBER}</td>
  </tr>
  <!-- END: row.tpl -->

Now we can start coding...

 /* START */

    <?
    include("class.FastTemplate.php3");
    $tpl = new FastTemplate("/path/to/templates");
    $tpl->define( array( main   => "main.tpl",
                         table  => "table.tpl",
                         row    => "row.tpl"    ));

    $tpl->cache_expire (main, 3600);
    $tpl->cache_refresh (main, "day");

    $tpl->assign(TITLE,"FastTemplate Test");

    if (!$tpl->is_cached (ROWS,".row"))
    {
       for ($n=1; $n <= 3; $n++)
       {
           $Number = $n;
           $BigNum = $n*10;
           $tpl->assign( array(  NUMBER      =>  $Number,
                                 BIG_NUMBER  =>  $BigNum ));

           $tpl->parse(ROWS,".row");
       }
       $tpl->write_cache (ROWS,".row");
    }
    $tpl->parse(MAIN, array("table","main"));
    Header("Content-type: text/plain");
    $tpl->FastPrint();
    exit;
    ?>

  When run it returns:

  <!-- NAME: main.tpl -->
  <html>
  <head><title>FastTemplate Test</title>
  </head>
  <body>
  <!-- NAME: table.tpl -->
  <table>
  <!-- NAME: row.tpl -->
  <tr>
  <td>1</td>
  <td>10</td>
  </tr>
  <!-- END: row.tpl -->
  <!-- NAME: row.tpl -->
  <tr>
  <td>2</td>
  <td>20</td>
  </tr>
  <!-- END: row.tpl -->
  <!-- NAME: row.tpl -->
  <tr>
  <td>3</td>
  <td>30</td>
  </tr>
  <!-- END: row.tpl -->
  
  </table>
  <!-- END: table.tpl -->

  </body>
  </html>
  <!-- END: main.tpl -->

If you're thinking you could have done the same thing in a few lines of plain PHP, well yes you probably could. But, how would a graphic designer tweak the resulting HTML? How would you have a designer editing the HTML while you're editing another part of the code? How would you save the output to a file, or pipe it to another application? How would you make your application multi-lingual? How would you build an application that has options for high graphics, or text-only? FastTemplate really starts to shine when you are building mid to large scale web applications, simply because it begins to seperate the application's generic logic from the specific implementation.


VERSION This is Revision 1.1.0 Jun 27, 13:20 CDI, cdi@thewebmasters.net The revision jumped from 0.8 to 1.1.0 since I've put all my source code into my own CVS repository now.


AUTHOR

Some caching functions by Benjamin Kahn xkahn@cybersites.com http://www.zoned.net/~xkahn/php/fasttemplate
Portions written by Benjamin Kahn Copyright (c) 2000 CyberSites, Inc; xkahn@cybersites.com, All Rights Reserved

Caching functions originally by: "JP"
http://www.phpbuilder.com/columns/jprins20000201.php3
with code by: Spencer D. Mindlin
http://www.phpbuilder.com/columns/spencer20000208.php3

PHP3 port by CDI cdi@thewebmasters.net
PHP3 Version Copyright (c) 1999 CDI, cdi@thewebmasters.net, All Rights Reserved.
Perl Version Copyright (c) 1998 Jason Moore jmoore@sober.com. All Rights Reserved.
Original Perl module CGI::FastTemplate by Jason Moore jmoore@sober.com

This program is free software; you can redistribute it and/or modify it under the GNU Library General Public License, with the following stipulations;

Changes or modifications must retain these Copyright statements. Changes or modifications must be submitted to all AUTHORS.

This program is released under the Library General Public License.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Artistic License for more details. This software is distributed AS-IS.

Address Bug Reports or Comments on THIS PHP VERSION ONLY to

    CDI, cdi@thewebmasters.net, or to
    CyberSites, Inc. (all caching related questions) xkahn@cybersites.com

The latest version of this class should be available from the following locations:

http://zoned.net/~xkahn/php/fasttemplate/


DOCUMENTATION

Sascha Schumann has written a very nice FastTemplate tutorial. It's on the PHPBuilder.com web site at;

http://www.phpbuilder.com/

This is a modified version of the CGI::FastTemplate man page, originally written by Jason Moore jmoore@sober.com. Forgive me if I didn't get all the Perlisms out of the example code.

This is not a complete port, the define_nofile(array()), and/or define_raw(array()) methods were not implemented in this port since I had no need or use for them. Some of the methods are implemented differently (mostly due to PHP's stronger variable type requirements.) The functionality of each method has remained the same. The define_dynamic() method is completely new to this PHP port and does not appear in the Perl version.

The variable declaration method has changed from the Perl version's $(A-Z0-9_)+ to {(A-Z0-9_)+}, which means you'll have to edit all your templates. The beginning and close curly braces allow for much faster and more accurate templates.


SEE ALSO

CGI::FastTemplate Perl module, available from CPAN - http://www.cpan.org