| Revision History | ||
|---|---|---|
| Revision v1.3 | 2008-01-27 | kl |
| Updated for PHPTAL 1.1.10 | ||
| Revision v1.2 | 2005-12-10 | ds |
| English Corrections by Dan Sheppard | ||
| Revision v1.1 | 2005-03-29 | lb |
| Added section about PHPTAL class | ||
| Revision v1.0 | 2005-03-27 | lb |
| Converted draft to docbook | ||
Table of Contents
PHPTAL is an implementation of the excellent Zope Page Template (ZPT) system for PHP. PHPTAL supports TAL, METAL, I18N namespaces.
PHPTALES is the equivalent of TALES, the Template Attribute Language Expression Syntax. It defines how XML attribute values are handled.
As PHPTALES is similar to TALES, it should be easy to port python TAL templates into PHP ones (and vice versa).
To be TAL compliant, PHPTAL implements XPATH-like access to data.
PHPTAL is freely distributed under the LGPL license, it is
developed by Laurent Bedubourg
<lbedubourg@motion-twin.com> and maintained by Kornel Lesiński.
XML/HTML templates exist to separate logic from presentation in web services. This separation brings more than one accompanying benefit.
better application design
easier task repartition
better maintainability
easy web skins
Most template systems uses <? ?>, <% %> or <xxx:yyy></xxx:yyy> tags to find their sections. It allows easier template system development but doesn't really help template designers.
The idea behind TAL is to allow WYSIWYG template editing with sample rendering without strange tags everywhere. That's why TAL works on XML attributes instead of markup tags.
If you have already worked with a simple template system, then you must have encountered something looking like:
<table>
<%loop myarray as myitem %>
<tr>
<td><% myitem %></td>
</tr>
<%/loop%>
</table>
Well, with phptal you now can write:
<table>
<tr tal:repeat="myitem myarray">
<td tal:content="myitem">
text replaced by the item value
</td>
<td tal:replace="">sample 1</td>
<td tal:replace="">sample 2</td>
<td tal:replace="">sample 3</td>
</tr>
</table>
In WYSIWYG mode, the above code will render correctly with the sample text, and you can present it to your clients even if the code required to get 'myarray' values doesn't yet exist.
Another big advantage of PHPTAL is that you benefit from more than 3 years of ZOPE community experience, documentation, examples, and help. PHPTAL relies on this community to provide its users a great deal of useful information.
PHPTAL is designed to be as customizable as possible for advanced developers and performance-eating systems, but still be easy to use for beginners, with a comfortable and simple default behaviour (at least I tried :)
PHPTAL is released as a PEAR package (see http://pear.php.net). You can download the PHPTAL library on the PHPTAL website (http://phptal.motion-twin.com).
You can install it using the pear utility:
pear install http://phptal.motion-twin.com/latest.tar.gz
Once installed, you can upgrade PHPTAL easily on each PHPTAL update using PEAR:
pear upgrade http://phptal.motion-twin.com/latest.tar.gz
If you do not use PEAR or do not have it installed on your system, you can still install PHPTAL by unzipping the downloaded archive.
tar zxvf PHPTAL-X.X.X.tar.gz cp -r PHPTAL-X.X.X/PHPTAL* /path/to/your/lib/folder
This will install the PHPTAL.php file and the associated PHPTAL folder in /path/to/your/lib/folder.
Don't forget to modify your php include path in your scripts so that PHP will be able to locate library files automatically:
<?php set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/your/lib/folder'); // will work like a charm require_once 'PHPTAL.php'; ?>
If you work with apache, you may also be able to modify the php include_path in a .htaccess file:
# some .htaccess file # linux users may use something as follow php_value include_path /usr/lib/php:/usr/local/lib/php:/path/to/your/lib/folder:.
You may also modify the include_path variable in you php.ini file.
To get a first impression of PHPTAL usage, a simple example is better than many words.
Your template is a valid xml/html document (with a root element). Here's a file named 'my_template_file.html'.
<?xml version="1.0"?>
<html>
<head>
<title tal:content="title">
Place for the page title
</title>
</head>
<body>
<h1 tal:content="title">sample title</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<tr tal:repeat="person people">
<td tal:content="person/name">person's name</td>
<td tal:content="person/phone">person's phone</td>
</tr>
<tr tal:replace="">
<td>sample name</td>
<td>sample phone</td>
</tr>
<tr tal:replace="">
<td>sample name</td>
<td>sample phone</td>
</tr>
</tbody>
</table>
</body>
</html>
In php, you just have to include the PHPTAL library, and maybe configure a few variables to customize the template system.
<?php
require_once 'PHPTAL.php';
// create a new template object
$template = new PHPTAL('my_template_file.html');
// the Person class
class Person {
public $name;
public $phone;
function Person($name, $phone) {
$this->name = $name;
$this->phone = $phone;
}
}
// let's create an array of objects for test purpose
$people = array();
$people[] = new Person("foo", "01-344-121-021");
$people[] = new Person("bar", "05-999-165-541");
$people[] = new Person("baz", "01-389-321-024");
$people[] = new Person("buz", "05-321-378-654");
// put some data into the template context
$template->title = 'The title value';
$template->people = $people;
// execute the template
try {
echo $template->execute();
}
catch (Exception $e){
echo $e;
}
?>
If you execute the php script, you will obtain something similar to what follows.
<?xml version="1.0"?>
<html>
<head>
<title>The title value</title>
</head>
<body>
<h1>The title value</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td>01-344-121-021</td>
</tr><tr> <td>bar</td>
<td>05-999-165-541</td>
</tr><tr> <td>baz</td>
<td>01-389-321-024</td>
</tr><tr> <td>buz</td>
<td>05-321-378-654</td>
</tr>
</tbody>
</table>
</body>
</html>
Well, I assume you don't mind 'too' much about carriage returns in the HTML code :) If so, you may use the tidy functions to cleanup the resulting html before echoing it.
Table of Contents
This section describes TAL and its extensions. It mainly targets template designers but must be read by PHP integrators as well.
It is important to note that the order of declaration of attributes is irrelevant.
For example,
<span tal:define="usersList application/listUsers"
tal:condition="somecondition"
tal:repeat="user usersList"
>...</span>
Is EXACTLY the same as:
<span tal:repeat="user usersList"
tal:condition="somecondition"
tal:define="usersList application/listUsers"
>...</span>
Priority is the same as described by TAL specifications:
define
condition
repeat
content or replace
attributes
omit-tag
This attribute defines one or more variables which may be used later in the template.
Making a shortcut to a long path:
<span tal:define="global destname path/to/existing/variable" />
Creating a string inside the template:
<span tal:define="global destname string:some string" />
Defining more than one variable at the same time:
<span tal:define="global fname string:paul; lname string:dupond" />
Defining a string containing another variable:
<span tal:define="global hello string:hello $fname welcome on this page" />
A small trick which uses output buffers:
<span tal:define="global hello">hello ${fname} welcome on this page</span>
You may also use tal:define with other attributes, it will be executed before any other attributes.
In above examples, the span tag won't show up because it has no printable content nor attributes. Even the last example does not show the message because the 'hello' variable grabs it.
On the other hand:
<span tal:define="hello string:hello ${fname} welcome on this page"
tal:content="hello"
/>
Will both set the hello variable and print it.
BUT the following is irregular because tal:define will calculate the content of the node before being executed and tal:content IS the content of the node. Whatever is inside the span is just ignored. Thus hello won't be defined and an exception will be thrown.
<span tal:define="hello" tal:content="hello">
hello ${fname} welcome on this page
</span>
In above examples, you may have spotten the 'global' keywords before some variables names. In PHPTAL you can either define a variable globally or locally.
A global variable will be accessible from any xml node of your templates or called macros.
<span tal:define="global hello string:hello world"/> <p tal:content="hello"/>
On the contrary, a local variable is only available inside the tag it is defined in:
<span tal:define="hello string:hello world"/> <p tal:content="hello"/> <!-- will produce an undefined variable error -->
The entity and its content will be shown only if the condition is evaluated to true.
<span tal:condition="identified"> Welcome member ... </span>
<span tal:condition="not: identified"> Please login before accessing this page </span>
If the php backend does not provide your templates with enough methods, you will often have to fall back to php for special conditions:
<span tal:comment="show only if more than five items in the cart"
tal:condition="php: cart.countItems() GT 5">...</span>
This may put too much logic inside the template and it is sometimes preferable to provide boolean attributes or accessible methods to the template:
<span tal:condition="cart/hasMoreThanFiveItems">...</span>
<span tal:condition="fullfillNumerousItems">...</span>
This attribute handles iterable objects like arrays, associative arrays, and objects implementing the PHP5 Iterable class.
The repeat attribute repeats its element and its content until the end of the specified resource.
<tr tal:repeat="item some/result"> <td tal:content="item">text replaced by item</td> </tr>
Within a loop, you can access the current loop information (and that of its parent for nested loops) using specific repeat/* paths.
In the above example:
repeat/item/index : return the item index (0 to count-1)
repeat/item/number : returns the item number (1 to count)
repeat/item/even : returns true if item index is even
repeat/item/odd : returns true if item index is odd
repeat/item/start : returns true if item is the first one
repeat/item/end : returns true if item is the last one
repeat/item/length : returns the number of elements in some/result
repeat/item/key : returns the item's key if some/result is an associative resource (index otherwise)
"item" depends on the receiver variable defined in tal:repeat expression.
The most common usage of tal:repeat is in using some SQL database result. Providing you use a library implementing the Iterator PHP interface:
<table>
<thead>
<tr>
<th>Position</th>
<th>Player</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr tal:repeat="ranking playersRanking">
<td tal:content="ranking/position"/>
<td tal:content="ranking/player"/>
<td tal:content="ranking/score"/>
</tr>
</tbody>
</table>
This attribute asks the PHPTAL parser to ignore the entity's open and close tag, its content will still be evaluated.
<span tal:omit-tag=""> only this text will appear, span open and close will be removed </span>
Will produce:
only this text will appear, span open and close will be removed
This attribute is useful when you want to define a macro, a loop, a condition, or any other template logic that mustn't show it's root tag.
This attribute will replace the entire tag with a value, or by nothing if no value is given.
<span tal:replace="string:this beautyfull string"> this uggly string and span </span>
Will produce:
this beautyfull string
tal:replace can also be used to create samples in source templates, but remove them from final output.
<table>
<tr tal:repeat="item myresult">
<td tal:content="item">item value</td>
</tr>
<tr tal:replace="">
<td>sample 1</td>
</tr>
<tr tal:replace="">
<td>sample 2</td>
</tr>
</table>
This attribute replaces the tag content with the evaluation of its expression.
<span tal:define="myvar string:my string"/> <span tal:content="myvar">will be replaced</span>
Will produce:
<span>my string</span>
This attribute changes tag attribute(s) value(s).
<a href="http://www.foo.com" title="some foo link" tal:attributes="href somelink/href; title somelink/title" tal:content="somelink/text" >sample link</a>
With a 'somelink' having:
$somelink->href = "http://www.google.com"; $somelink->title = "google search engine"; $somelink->text = "the google search engine";
Will produce:
<a href="http://www.google.com" title="google search engine">the google search engine</a>
A somewhat complicated example involving tal:repeat:
<tr tal:repeat="ranking playerRankings"
tal:attribute="class php: repeat.ranking.odd ? 'odd' : false">
...
</tr>
The php: modifier will be explained later, basically if the line is odd then tr will have a class attribute with "odd" as value, otherwise, no class attribute will be set.
The "condition ? then : else" is a regular PHP expression which must be used with care but has proven to be useful on more than one occasion.
A better way to achieve the same result would be to ask your PHP coder to create a custom modifier for your needs (see PHP integration / custom modifiers) which would be used as follows:
<tr tal:repeat="ranking playerRankings"
tal:attribute="class css-odd:repeat/ranking/odd">
...
</tr>
The modifier would return "odd" if repeat/ranking/odd is true, false otherwise.
This attribute replaces the tag by the tal:on-error expression evaluation if a path error is detected in the tag content, or if any php exception is thrown in the tag content.
<span tal:on-error="string:No username defined here"
tal:content="user/name">the user name here</span>
If an error occurs accessing 'name' or 'user', the error string will be shown at the tag's place.
This also works on more than one level of template:
<span tal:on-error="string:error occurred somewhere"> <span tal:content="user/firstname"/> <span tal:content="user/lastname"/> <span metal:use-macro="userMenu" /> </span>
METAL stands for 'Macro Extension for TAL'. This namespace, supported by PHPTAL, allows template designers to define and call xml/xhtml macros.
This attribute declares a macro. Think of macros as library of small templates which can be reused in any other template.
<div metal:define-macro="main_menu">
<ul>
<li><a href="/">home</a></li>
<li><a href="/products">products</a></li>
<li><a href="/contact">contact</a></li>
</ul>
<div>
Last modified:
<span tal:content="mdate">page modification date</span>
</div>
</div>
Macros inherit from the caller's dictionary. In the above example, the variable 'mdate' depends on the template that calls the macro.
This attribute calls a macro and includes its result in the current template.
<span tal:comment="main_menu template requires 'mdate' variable" tal:define="mdate page/last_modified" metal:use-macro="main_menu" />
You can refer to external macros defined in other templates by specifying the template source file.
<span metal:use-macro="site_macros.html/main_menu"/>
It is interesting to note that you can also use the PHPTAL inline replacement feature inside the use-macro attribute value:
<span metal:use-macro="${design}/site_macros.html/main_menu"/>
This attribute must appear under a metal:define-macro tag.
Slots can be replaced by caller template with some custom dynamically-generated XML/XHTML.
Slots can be thought of like reverse includes, a macro can be an entire page and slots customize this page depending on the URL. For instance, a slot may contain the latest news in the home page or user actions when the member is logged in.
<span metal:define-slot="news_place">
<table>
<tr tal:repeat="item php:latestNews()">
<td tal:content="item/value">news description</td>
</tr>
</table>
</span>
The above example defines a place called 'news_place' which can be overwritten by caller templates. See next section for the continuation of this example.
This attribute occurs only under metal:use-macro context.
This explicitly tells PHPTAL to replace a defined slot with the content provided under the metal:fill-slot attribute.
<span tal:condition="logged" metal:fill-slot="news_place">
<h2>user menu</h2>
<ul>
<li><a href="/user/action/inbox">inbox</a></li>
<li><a href="/user/action/new">new mail</a></li>
<li><a href="/user/action/disconnect">disconnect</a></li>
</ul>
</span>
Slots give the opportunity to define really customizable and reusable page templates with a simple push technology.
Note: 'i18n' is a short name for 'internationalization'. This namespace allow template designers to specify some text zones that must be translated during template evaluation.
This attribute defines some text part that must be translated using PHPTAL's translation system.
<div i18n:translate="string:welcome_message">Welcome here</div>
In the above example, PHPTAL will looks for a translation key named 'welcome_message' and will replace the content of the tag with the equivalent in currently requested language.
<div i18n:translate="">Welcome here</div>
This usage is a little different, no translation key is given, thus PHPTAL will use the content of the tag 'Welcome here' as the translation key. This is a regular translation if translation system knows the key 'Welcome here'.
If no translation is found, the key will be used as the translation result. That's why using readable message instead of keys may be a good choice.
Please note that the key to translate may be contained in a variable, to allow dynamic key selection.
<div tal:define="welcome random_welcome_message"/> <did i18n:translate="welcome">...</div>
This attribute sets a translation variable value.
Translations may contain ${xxx} strings where "xxx" is the name of a variable that needs to be interpolated dynamically.
The value of this variable will be set to the tag and its content. If you don't need tag around the value, use tal:replace instead of tal:content. tal:omit-tag may help if the value is a concatenation of strings.
<span i18n:name="myVar" tal:content="some/path"/>
<!-- <span>${some/path}</span> -->
<span i18n:name="myVar" tal:replace="some/path"/>
<!-- ${some/path} -->
<span i18n:name="myVar">foo</span>
<!-- <span>foo</span> -->
<span i18n:name="myVar" tal:omit-tag="">foo</span>
<!-- foo -->
An example of i18n usage:
<div i18n:translate=""> Welcome <span i18n:name="user" tal:replace="user/name"/>, you have <span i18n:name="mails" tal:replace="user/nbrMails"/> unread mails. </div>
The translation key of this example will be:
"Welcome ${user}, you have ${mails} unread mails."
PHPTAL will replace ${user} with ${user/name} and ${mails} with ${user/nbrMails} in translation.
More information about I18N with PHPTAL is available in the PHP section of this book.
These attributes are not defined in TAL specifications, but are useful when working with PHPTAL.
This attribute toggles the activation of PHPTAL debugging for the content of the tag it is defined in.
The debug mode stores information like filename and source line number in the template, so exceptions thrown by incorrect path access will contains more information about where they where thrown.
<html>
<head>
...
</head>
<body>
<div id="menu">
...
</div>
<div id="leftPane" phptal:debug=""
tal:comment="this div seems buggy, keep
trace of where errors are thrown">
...
</div>
</body>
</html>
This attribute causes output of entire element (including its tag) to be cached on disk and not re-evaluated until cache expires.
Content of this attribute is duration (how long element should be kept in cache) written as number with 'd', 'h', 'm' or 's' suffix.
<div class="footer" phptal:cache="3h">...</div>
<div> will be evaluated at most once per 3 hours.
Duration can be followed by optional "per" parameter that defines how cache should be shared. By default cache is shared between all pages that use that template. You can add "per url" to have separate copy of given element for every URL.
<ol id="breadcrumbs" phptal:cache="1d per url">...</ol>
<ol> will be cached for one day, separately for each page.
You can add "per expression" to have different cache copy for every different value of an expression (which MUST evaluate to a string). Expression cannot refer to variables defined using tal:define on the same element.
<ul id="user-info" phptal:cache="25m per object/id">...</ul>
<ul> will be cached for 25 minutes, separately for each object ID.
Limitations:
phptal:cache blocks can be nested, but outmost block will cache other blocks regardless of their freshness.
You cannot use metal:fill-slot inside elements with phptal:cache.
This attribute allows us to change the behaviour of PHPTALES. The default behaviours is to interpret attribute expressions in a very ZPT way. But sometimes you just would like to have PHP there, and you end up using php: modifier everywhere.
Another problem concerning PHPTALES is the way PHPTAL has to interpret paths. For example, myobject/mymethod/10/othermethod/hashkey takes a very long to interpret.
PHPTAL has (at runtime) to take myobject and discover that it is an object; find out that 'mymethod' is a method of this object (rather than a variable), and then to call it; explore the result to determine that it is an array; find the 'ten' element of this array, and determine that it is an object; decide that othermethod is a method of this object (rather than a variable), and get the result of its execution; find that it is an associative array, and then retrieve the value for the key 'hashkey'.
Of course this was an extreme example and most of the time we don't care, because the process is fast enough. But what if this very long path is called inside a big tal:repeat ? D'oh! phptal:tales can help us here:
<html>
<body>
<table phptal:tales="php">
<tr tal:repeat="myobject document.getChildren()">
<td
tal:content="myobject.mymethod()[10].otherMethod()['hashkey']"></td>
</tr>
</table>
</body>
</html>
Well, I am not sure that the example above compiles, but you get the idea of what you can do with phptal:tales. All tal, metal, i18n paths will be plain PHP expressions.
Please note that the above example do the same as:
<html>
<body>
<table>
<tr tal:repeat="myobject php:document.getChildren()">
<td
tal:content="php:myobject.mymethod()[10].otherMethod()['hashkey']"></td>
</tr>
</table>
</body>
</html>
'php:' modifier is explained in its own chapter.
:block is a syntactic sugar for elements which contains many tal attributes which are not to be echoed.
<tal:block define="myvar string:Some value"/>
is the same as:
<span tal:define="myvar string:Some value"/>
Another example:
<tal:block condition="someCondition" repeat="item someRepeat"> <div metal:use-macro="x"/> </tal:block>
is the same as:
<div tal:omit-tag=""
tal:condition="someCondition"
tal:repeat="item someRepeat">
<div metal:use-macro="x"/>
</div>
PHPTALES is the expression syntax used inside tal, metal, phptal attributes. From above examples, you should have seen some PHPTALES examples (string:, php:, not:, ...). This chapter describes the usage of PHPTALES in templates.
The value of a TAL attribute may contain more than one expression (ex: tal:define), in which case each expression must be separated from the next one with a ';' character.
This is the default modifier used in TAL expression when no other modifier is specified.
The following lines will give the same result:
<span tal:content="data/user/name"/>
<span tal:content="path:data/user/name"/>
<span>${data/user/name}</span>
Inside the template or inside expression strings, you can refer to a context variable using its path in the form ${path/to/my/variable}
<h1>${document/title}</h1>
<span tal:replace="string:welcome ${user/name},
this page has been readed ${page/countRead} times"/>
As '<' and '>' should be removed from attribute expression. PHPTAL provides some equivalent good old text-comparison operators.
These statements will mostly appear in tal:condition attributes, and in php: expressions.
< : LT (less than)
> : GT (greater than)
<= : LE (less or equal)
>= : GE (greater or equal)
Because expressions are separated by a ';' character, and because '$' marks the start of a path, you must use:
';;' when you want to insert a real ';' character in a string,
'$$' when you want to insert a real '$' character in a string.
<span tal:replace="string:this is a $$100 page"/> string:foo $bar baz <!-- will replace $bar --> string:foo $$bar baz <!-- no interpolation --> string:foo ; php:doFoo() <!-- two different expressions --> string:foo ;; php:doFoo() <!-- only string -->
This expression evaluates what follows as a regular php expression except that '->' are replaced by dots '.' and variable names does not need to be prefixed with a dollar '$' sign.
A dot '.' separated from the rest of expression by spaces is assumed to be a concatenation sign.
php:htmlentities(foo)
php:'string ${varReplaced}'
php:'string ${some.path().to[0].var}'
php:NOT foo OR (bar GT baz)
php:a + b
php:array('a', 'b', 'c')
php:range(0, 90)
php:foo . a.b.c(e) . htmlentities(SomeClass::staticMethod())
php:SomeClass::ConstOfClass
php:SomeClass::$staticVar
php: should be used with care and won't be needed in 80% of your templates but sometimes you will need to invoke some special php method to be certain whether a user is logged in, or to retrieve specific complex data depending on some conditions, dynamically inside the template.
This expression is a boolean one, useful in tal:condition statements.
<span tal:condition="not: logged">not logged</span>
This expression is a boolean expression, it will returns true if the path specified after it exists of not.
<span tal:condition="exists: user/preferences"
tal:content="user/preferences">
user preferences here if defined
</span>
It is important to keep in mind that using a path which doesn't exist will throw and exception. Thus, uncertain paths must be checked first.
This is not an expression but a keyword, allowing template designers to keep the content of a tag as an alternative value if an error occurs, or if something is not defined.
<span tal:define="myVar path/to/possible/var | default"> default my var value </span> <span tal:content="some/var | other/path | default"> no some/var and no other/path found here </span> <a href="unknown.html" title="Unknown page" tal:attributes="href item/href | default; title item/title | default" tal:content="item/title | default">Unknown page</a>
Above examples introduce the '|' character that allows the definition of alternatives for defines or prints.
This is not an expression modifier but a keyword.
While printing variables inside PHPTAL templates, you will have noticed that PHPTAL encodes each variable to ensure the validity of the output document.
Sometimes, you may use HTML/XML variables which must be echoed as is.
<h1 tal:content="structure document/title"/> <span tal:replace="structure document/content"/>
In above examples, we assume that $document->title and $document->content are variables containing preformated HTML which must be echoed as is.
An expression chain is a list of expressions separated by '|' characters.
While evaluating expressions separated by '|', PHPTAL will stop its evaluation when an expression value is not null and no error was raised while evaluating the expression.
As a string: expression is always true, string: always terminates an expression chain whatever expression may follow.
You can use php: expressions inside expression chains, like any other expression.
<h1 tal:content="page/title | page/alternativeTitle | default> untitled page </h1>
Table of Contents
This section is aimed at PHP developers and explains how to use and customize PHPTAL behaviours for simple and advanced usage.
Defines: list of PHPTAL defines and defines influencing PHPTAL
PHPTAL: the main PHPTAL class
PHPTAL_Filter : filtering template sources and PHPTAL output
PHPTAL_Trigger: creating triggers for phptal:id
PHPTAL_TranslationService: replacing the builtin gettext support with your own internationalization system
After the inclusion of PHPTAL library, some defines will be created in PHP context, all these defines come from PHPTAL.php file:
PHPTAL_VERSION: version of PHPTAL library installed on your system (in format: X.X.X)
PHPTAL_PHP_CODE_DESTINATION: this is the path where intermediate PHP files produced by PHPTAL will be stored. This define may be overwritten before the inclusion of PHPTAL.php, the specified path must contain the leading path separator.
You can configure some PHPTAL features by defining some constants:
To tell PHPTAL to ignore intermediate php files and to reparse templates every time:
<?php
define('PHPTAL_FORCE_REPARSE', 1);
require_once 'PHPTAL.php';
?>
To tell PHPTAL to store its intermediate PHP files other than in the '/tmp/' directory:
<?php
define('PHPTAL_PHP_CODE_DESTINATION', '/path/to/somewhere/');
require_once 'PHPTAL.php';
?>
If all your files are stored in a base directory (repository), you can define PHPTAL_TEMPLATE_REPOSITORY as follows:
<?php
define('PHPTAL_TEMPLATE_REPOSITORY', '/path/to/templates/root');
require_once 'PHPTAL.php';
?>
This doesn't mean all your files need to be in the root directory, you can use sub folders to organize your template designer's work. It's just a shortcut which will allow you to reference templates without specifying the real path, but instead their relative path within the repository.
This is the main library class for you to use.
The most common method of use:
<?php
// include the library
require_once 'PHPTAL.php';
// instantiate a new PHPTAL object using specified template file
$tpl = new PHPTAL('mytemplate.html');
// setting some template context variables
$tpl->title = 'my title';
$tpl->values = array(1,2,3,4);
$tpl->user = new User('Joe');
// execute the template and echo the result in a 'secure' way
try {
echo $tpl->execute();
}
catch (Exception $e){
echo "Exception thrown while processing template\n";
echo $e;
}
?>
You can perfectly well choose to specify the template source after setting context variables.
<?php
...
$tpl = new PHPTAL();
// it is a matter of taste but you can use the set() method instead of
// setting context using PHPTAL::__set() like above
$tpl->set('title', 'my title');
$tpl->set('values', array(1,2,3,4));
$tpl->set('user', new User('Joe'));
$tpl->setTemplate('mytemplate.html');
...
?>
You can also decide to use a generated string as the template source instead of using an existing template file:
<?php
$src = <<<EOS
<html>
<head>
<title tal:content="title">my title</title>
</head>
<body>
<h1 tal:content="title">my title</h1>
</body>
</html>
EOS;
require_once 'PHPTAL.php';
$tpl = new PHPTAL();
$tpl->setSource($src);
$tpl->title = 'this is my title';
try {
echo $tpl->execute();
}
catch (Exception $e){
echo $e;
}
?>
In the above example, because PHPTAL requires a template source idenfifier (usually the template file realpath), PHPTAL will use the md5 of the $src parameter as a unique identifier. You may decide to force the identifier using a second setSource() argument:
<?php
$src = <<<EOS
<html>
<head>
<title tal:content="title">my title</title>
</head>
<body>
<h1 tal:content="title">my title</h1>
</body>
</html>
EOS;
require_once 'PHPTAL.php';
$tpl = new PHPTAL();
// because the source is contained in this file and won't be modified unless
// this file is modified, it is 'faster' to specify __FILE__ as the unique
// source identifier, thus no md5 of $src will be done on each call.
$tpl->setSource($src, __FILE__);
$tpl->title = 'this is my title';
try {
echo $tpl->execute();
}
catch (Exception $e){
echo $e;
}
?>
This interface allows you to automatically filter templates sources (pre-filters) or PHPTAL result (post-filters).
Pre filters are invoked before the template parsing and won't be invoked until the source template file is modified.
Post filters are invoked after each template execution.
<?php
require_once 'PHPTAL.php';
class MyPreFilter implements PHPTAL_Filter {
public function filter($source){
return $source;
}
}
class MyPostFilter implements PHPTAL_Filter {
public function filter($xhtml){
return $xhtml;
}
}
$tpl = new PHPTAL('mytemplate.html');
$tpl->setPreFilter(new MyPreFilter());
$tpl->setPostFilter(new MyPostFilter());
echo $tpl->execute();
?>
You can set only one pre-Filter and one post-Filter using set*Filter. If you have more than one filter to chain, you can wrap them into a single class, implementing the PHPTAL_Filter interface, which would invoke the filter's chain.
<?php
require_once 'PHPTAL.php';
class FilterChain implements PHPTAL_Filter {
private $_filters = array();
public function add(PHPTAL_Filter $filter){
$this->_filters[] = $filter;
}
public function filter($source){
foreach ($this->_filters as $filter){
$source = $filter->filter($source);
}
return $source;
}
}
$myfilter = new FilterChain();
$myfilter->add(new CommentFilter()); // imaginary filter
$myfilter->add(new TidyFilter()); // imaginary filter
$tpl = new PHPTAL('mytemplate.html');
$tpl->setPostFilter($myFilter);
echo $tpl->execute();
?>
The phptal:id attribute was added into the PHPTAL for the PHP5 version to replace the old PHPTAL_Cache interface and to abstract it a little more.
When a phptal:id is reached, PHPTAL will look in its triggers list for a matching id and will invoke the trigger start() and end() methods before entering the element, and just after it.
If the PHPTAL_Trigger::start() methods returns PHPTAL_Trigger::SKIPTAG, PHPTAL will ignore the element and its content (start() may echo something to replace it).
If your trigger wants the element and its content to be executed, you'll have to return PHPTAL_Trigger::PROCEED.
The PHPTAL_Trigger::end() will be called after the element (whether it has been executed or not). This allows you to build cache systems using ob_start() in start() and ob_get_contents(), ob_end_clean() in end().
<html>
...
<div>
...
foo bar baz <span tal:replace="id"/> foo bar baz
...
</div>
...
</html>
For some reason we decide the div block requires to be cached. We introduce a phptal:id into the template:
<html>
...
<div phptal:id="somePossiblyUniqueKeyword">
...
foo bar baz <span tal:replace="id"/> foo bar baz
...
</div>
...
</html>
Then we write our trigger which will cache the div content:
<?php
require_once 'PHPTAL.php';
require_once 'PHPTAL/Trigger.php';
class CacheTrigger implements PHPTAL_Trigger
{
public function start($phptalid, $tpl)
{
// this cache depends on 'id' which must appears in
// the template execution context
$this->_cachePath = 'cache.' . $tpl->getContext()->id;
// if already cached, read the cache and tell PHPTAL to
// ignore the tag content
if (file_exists($this->_cachePath)){
$this->_usedCache = true;
readfile($this->_cachePath);
return self::SKIPTAG;
}
// no cache found, we start an output buffer and tell
// PHPTAL to proceed (ie: execute the tag content)
$this->_usedCache = false;
ob_start();
return self::PROCEED;
}
// Invoked after tag execution
public function end($phptalid, $tpl)
{
// end of tag, if cached file used, do nothing
if ($this->_usedCache){
return;
}
// otherwise, get the content of the output buffer
// and write it into the cache file for later usage
$content = ob_get_contents();
ob_end_clean();
echo $content;
$f = fopen($this->_cachePath, 'w');
fwrite($f, $content);
fclose($f);
}
private $_cachePath;
private $_usedCache;
}
?>
The key here is to return from start() with either SKIPTAG or PROCEED.
When SKIPTAG is returned, PHPTAL will just ignore the tag and call end(). This usually means that the trigger takes the hand in deciding what to show there.
When PROCEED is returned, PHPTAL will execute the tag and its content as usual, then call end(). This allows our cache class to play with output buffers to execute the tag once and to store the result in a file which will be used in later calls.
To install our trigger we use:
<?php
require_once 'PHPTAL.php';
require_once 'CacheTrigger.php'; // our custom trigger
$trigger = new CacheTrigger();
$tpl = new PHPTAL('test.html');
// this trigger will only be called for phptal:id="triggerId"
$tpl->addTrigger('somePossiblyUniqueKeyword', $trigger);
$tpl->id = 1;
echo $tpl->execute();
?>
You can add as many triggers as you like to your templates. A generic cache trigger may also handle more than one phptal:id... etc...
PHPTAL comes with a default gettext translation service, as shown in another section. For some reason you may prefer to implement your own service of translation.
The PHPTAL_TranslationService interface is here to serve your needs.
The usage of your service will be the same as the PHPTAL_GetTextTranslator.
$tpl->setTranslator($yourOwnTranslatorInstance);
Your implementation must define the following methods:
This method may be called by the template to change the current output language.
Its arguments are a list of possible languages (use func_get_args() to get the argument array). The first known language should be used by your service.
<?php
require_once 'PHPTAL/TranslationService.php';
class MyTranslator implements PHPTAL_TranslationService {
...
public function setLanguage(){
$langs = func_get_args();
foreach ($langs as $lang){
// if $lang known use it and stop the loop
$this->_currentLang = $lang;
return;
}
}
...
private $_currentLang;
}
?>
If you decided to store your translations into separate files, one for each application, for example, this method allows you to select the translation domain from your templates (i18n:domain).
<?php
require_once 'PHPTAL/TranslationService.php';
class MyTranslator implements PHPTAL_TranslationService {
...
public function useDomain($domain){
if (!array_key_exists($domain, $this->_domains)){
$file = "domains/$this->_currentLang/$domain.php";
$this->_domains[$domain] = include($file);
}
$this->_currentDomain = $this->_domains[$domain];
}
...
private $_currentDomain;
private $_domains = array();
}
?>
The above example is a possible translation solution where keys are stored in php files which return an associative array of key => translation.
This method matches i18n:name calls. It builds an interpolation context for later translate calls.
<?php
require_once 'PHPTAL/TranslationService.php';
class MyTranslator implements PHPTAL_TranslationService {
...
public function setVar($key, $value){
$this->_context[$key] = $value;
}
...
private $_context = array();
}
?>
The last and most important method to implement, it asks your service to translate the specified key for the currently selected language.
<?php
require_once 'PHPTAL/TranslationService.php';
class MyTranslator implements PHPTAL_TranslationService {
...
public function translate($key){
$value = $this->_currentDomain[$key];
// interpolate ${myvar} using context associative array
while (preg_match('/\${(.*?)\}/sm', $value, $m)){
list($src,$var) = $m;
if (!array_key_exists($var, $this->_context)){
$err = sprintf('Interpolation error, var "%s" not set',
$var);
throw new Exception($err);
}
$value = str_replace($src, $this->_context[$var], $value);
}
return $value;
}
...
}
?>
gettext is a standard GNU internationalization
/ translation system which can be used with PHP and which is
supported by PHPTAL.
The usage of gettext is simple but you will
have to perform some tests to be sure everything works fine on your
system.
First, PHP must be compiled with the
--with-gettext flag. See PHP documentation
for how to do this.
You can test your installation using following peace of code:
//
// test if gettext extension is installed with php
//
if (!function_exists("gettext"))
{
echo "gettext is not installed\n";
}
else
{
echo "gettext is supported\n";
}
The PHP gettext extension requires a specific structure which will contain your translation files.
/path/to/your/translation_root/en_US/LC_MESSAGES/ /path/to/your/translation_root/en_GB/LC_MESSAGES/ /path/to/your/translation_root/fr_FR/LC_MESSAGES/ /path/to/your/translation_root/es_ES/LC_MESSAGES/ ... and so on ...
The language code is composed of two characters defining the language itself (en, fr, es, ...) and two characters defining the country (US, GB, FR, ES, ...).
The directory pattern is:
<path_to_where_you_want>/<ll_CC>/LC_MESSAGES/
PO files are plain text files that contain your translation. You can safely edit them by hand.
po minimalistic example (en_US/LC_MESSAGES/mydomain.po):
msgid "" msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Simple test" msgstr "A small sentence in english"
Once edited, each PO file must be indexed using:
msgfmt mydomain.po -o mydomain.mo
This command won't work if you don't have gettext tools installed on your system.
This will produce a MO file (machine object) indexing your translation for quick access.
Then you have to translate this file in other languages.
po minimalistic example (fr_FR/LC_MESSAGES/mydomain.po):
msgid "" msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Simple test" msgstr "Une petite phrase en français"
The translation file must also be indexed:
msgfmt mydomain.po -o mydomain.mo
The domain is matched against your translation file names. In above examples we used 'mydomain' as domain name.
You can have more than one domain for the same application, it can enhance gettext's performance to split your application translations in more than one file.
<?php
require_once 'PHPTAL.php';
require_once 'PHPTAL/GetTextTranslator.php';
try {
$tr = new PHPTAL_GetTextTranslator();
// set language to use for this session (first valid language will
// be used)
$tr->setLanguage('en_GB.utf8', 'en_GB');
// register gettext domain to use
$tr->addDomain('mydomain', '/path/to/your/translation_root/');
// specify current domain
$tr->useDomain('mydomain');
$tpl = new PHPTAL('mytemplate.html');
// tell PHPTAL to use our translator
$tpl->setTranslator($tr);
}
catch (Exception $e){
echo $e;
}
The I18N namespace allows some variable interpolation in your translations.
# english
msgid "welcome"
msgstr "Welcome ${name} you have ${n} mails !"
# french
msgid "welcome"
msgstr "Bienvenue ${name} vous avez recu ${n} messages !"
A template can use this interpolation as follows:
<span i18n:translate="welcome"> Welcome <span i18n:name="name" tal:replace="user/name"/> you currently have <span i18n:name="n" tal:replace="user/unreadeMails"/> unread messages ! </span>
Because i18n:translate contains a value 'welcome', the template data will be ignored and the message given by gettext will be used instead.
PHPTAL comes with some basic expression modifiers (not:, exists:, string:, php:, path:).
These modifiers are defined by ZPT specifications but PHPTALES can be extended with your own modifiers to manipulate strings, date, money numbers, objects, whatever...
The aim of a modifier is to return some PHP code that will be included in the template php source.
Modifiers are used at parse time. If you change the behaviour of a modifier, you'll have to delete generated php files and reparse all templates using it.
Please note that modifiers produce code, and mustn't echo data!
Any php function starting with "phptal_tales_" is usuable as a modifier.
Modifiers takes two arguments:
$src: the source string after the "modifier:" keyword
$nothrow: a boolean which determines whether exceptions may be thrown or not by phptal_path() resolution. This boolean must be propagated whenever you call another phptal_tales_* modifier from within your own modifier.
For example, in the following TAL template,
<span tal:replace="some-modifier: my/path/value"/>
The src argument will be "my/path/value", and the $nothrow boolean will be false, because tal:replace requires the path to be fully resolvable.
An expression like:
<span tal:replace="some-modifier: my/path/value | other/path"/>
Will use 2 modifiers:
some-modifier: with "my/path/value" as $src argument and $nothrow set to true because an alternative exists
path: with "other/path" as $src, and $nothrow set to false because in case the alternative is not found, tal:replace will be in trouble.
Remember, path: is the implicit modifier used when no other modifier is specified.
Modifiers can use other modifiers to generate simpler php code. The example below shows this.
//
// This modifier will return a money formated string (XXX.XX)
//
// usage:
//
// money: path/to/my/amount
//
// this modifier use phptal_tales_path (path:) modifier to generate the
// php code that will return the value of the modifier argument.
//
// in the example:
//
// money: path/to/my/amount
//
// the expression "virtually" became money: path: path/to/my/amount
//
// thus, the produced code will be something looking like:
//
// sprintf("%01.2f", phptal_path($ctx->path, "to/my/amount"))
//
// This code will be included right into the template where needed.
//
// @param string $src
// The expression string
// @param string $nothrow
// A boolean indicating if exceptions may be throw by phptal_path if
// the path does not exists.
// @return string
// PHP code to include in the template
//
function phptal_tales_money( $src, $nothrow )
{
// remove spaces we do not require here
$src = trim($src);
return 'sprintf("%01.2f", '.phptal_tales_path($src, $nothrow).')';
}