PHPTAL

Атрибутивный Язык Шаблонов для PHP

Laurent Bédubourg

Kornel Lesiński

Dan Sheppard

Антон Валериевич Андриевский

История переиздания

Содержание

  1. Вступление
  2. Почему PHPTAL?
  3. Установка
  4. Пример
  5. Атрибутивный Язык Шаблонов TAL
    1. Приоритет атрибутов
    2. Пространство имен TAL
      1. tal:define
      2. tal:condition
      3. tal:repeat
      4. tal:omit-tag
      5. tal:replace
      6. tal:content
      7. tal:attributes
        1. Опциональные атрибуты
      8. tal:on-error
    3. Пространство имен METAL
      1. metal:define-macro
      2. metal:use-macro
      3. metal:define-slot
      4. metal:fill-slot
    4. Пространство имен I18N
      1. i18n:translate
      2. i18n:attributes
      3. i18n:name
      4. XHTML в переводах
    5. Пространство имен PHPTAL
      1. phptal:debug
      2. phptal:cache
        1. Принудительное обновление
        2. Ограничения:
      3. phptal:tales
    6. tal:block
    7. PHPTALES
      1. path:
      2. Alternative PHP operator syntax
      3. string:
      4. php:
      5. not:
      6. exists:
      7. default
      8. structure
      9. Expression chains
  6. PHP Integration
    1. Constants
    2. Configuration methods
    3. class PHPTAL
    4. interface PHPTAL_Filter
    5. interface PHPTAL_Trigger
    6. interface PHPTAL_TranslationService
      1. method setLanguage()
      2. method useDomain($domain)
      3. method setVar($key,$value)
      4. method translate($key)
      5. method setEncoding($encoding)
    7. Working with gettext
      1. Creating the translation directory structure
      2. Portable Object files
      3. Translation Domain
      4. Using Translator in PHP
      5. Variable interpolation
    8. Creating custom expression modifiers
  7. Note for system administrators
  8. Useful links
  9. Greetings

Вступление


PHPTAL является PHP реализацией системы шаблонов Zope Page Template (ZPT). PHPTAL поддерживает пространства имен TAL, METAL и I18N.

PHPTALES является эквивалентом TALES, Template Attribute Language Expression Syntax (синтаксис атрибутивного языка шаблонов). Он определяет, как обрабатываются значения атрибутов XML.

Так как PHPTALES подобен TALES, TAL шаблоны python и PHP могут одинаково использоваться и транспортироваться из одного языка в другой.

Чтобы быть совместимым с TAL, PHPTAL реализует XPath-подобный доступ к данным.

PHPTAL разработан Laurent Bedubourg и свободно распространяется под лицензией LGPL(и в данный момент поддерживается Kornel Lesiński).

Почему PHPTAL?


XML/HTML шаблоны отделяют логику скрипта (обработку данных) от представления (отображения этих данных) в веб. Такое отделение имеет ряд преимуществ.

  • более удобное проектирование программы/скрипта

  • простота разделения задач между программистом и дазийнером

  • упрощение поддержки после разработки

  • простота создания веб скинов (видов)

Большинство шаблонных языков используют теги <? ?>, <% %> или <xxx:yyy></xxx:yyy> чтобы определить, где и какое значение подставить в шаблон. Это упрощает разработку сайта, но никак не помогает разработчикам шаблонов.

TAL для описания логики использует XML атрибуты, не затрагивая синтакс и структуру XHTML. Это дает нам возможность предосмотра TAL шаблона в браузере (или в WYSIWYG редакторе) и не нарушает подсветку HTML синтаксиса в редакторе программиста.

Если Вы уже использовали какие-либо системы шаблонов, то наверняка сталкивались с чем-то вроде этого:

<table>
  <%loop myarray as myitem %>
  <tr>
    <td><% myitem %></td>
  </tr>
  <%/loop%>
</table>

В PHPTAL можно описать тоже самое проще и красивее:

<table>
  <tr tal:repeat="myitem myarray">
    <td tal:content="myitem">
      Текст, который будет заменен значением myitem
    </td>
    <td tal:replace="">Пример 1</td>
    <td tal:replace="">Пример 2</td>
    <td tal:replace="">Пример 3</td>
  </tr>
</table>

Такой шаблон будет корректно отображен в браузере с примерами "Пример 1", "Пример 2" и "Пример 3", поэтому можно продемонстрировать его клиенту даже на той стадии, когда код, необходимый для получения значений 'myarray', еще не существует.

Другое важное преимущество PHPTAL состоит в том, что Вы получаете доступ к помощи и более чем трехлетним опыту, документации и примерам сообщества Zope. Пользователи PHPTAL являются частью этого сообщества и автоматически получают от него исчерпывающую и полезную информацию

Главная сила PHPTAL в том, что для "продвинутых" программистов и ресурсоемких систем он настраиваемый настолько, насколько только это возможно, и в то же время очень прост и комфортнен для любыго новичка

Установка


PHPTALреализован в виде PEAR библиотеки (см. http://pear.php.net). Библиотеку PHPTAL можно скачать на сайте PHPTAL (http://phptal.org).

Eстановку можно выполнить с помощью утилит pear:

pear install http://phptal.org/latest.tar.gz

После установки, так же просто можно обновлять PHPTAL, используя PEAR:

pear upgrade http://phptal.org/latest.tar.gz

Можно обойтись и без PEAR, попросту разархивировав архив.

tar zxvf PHPTAL-X.X.X.tar.gz
cp -r PHPTAL-X.X.X/PHPTAL* /path/to/your/lib/folder

Эти инструкции установят файл PHPTAL.php и соответствующую папку PHPTAL в директорию /path/to/your/lib/folder.

Пример


Простой пример намного интереснее и познавательнее, чем многие слова

Ваш шаблон всегда будет синтаксически правильным xml/html документом (с присутствующим корневым элементом)! Вот пример файла под названием 'my_template_file.xhtml'.

<?xml version="1.0"?>
<html>
  <head>
    <title tal:content="title">
      Место для заголовка страницы
    </title>
  </head>
  <body>
    <h1 tal:content="title">Пример заголовка</h1>
    <table>
      <thead>
        <tr>
          <th>Имя</th>
          <th>Телефон</th>
        </tr>
      </thead>
      <tbody>
        <tr tal:repeat="person people">
          <td tal:content="person/name">Чье-то имя</td>
          <td tal:content="person/phone">Чей-то телефон</td>
        </tr>
        <tr tal:replace="">
          <td>Олег Радченко</td>
          <td>5226611</td>
        </tr>
        <tr tal:replace="">
          <td>Олег Радченко</td>
          <td>5226611</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

Все, что Вам понадобиться сделать в php - это включить (include) библиотеку PHPTAL и, возможно, установить несколько переменных, чтобы настроить шаблонную систему.

<?php
require_once 'PHPTAL.php';

// создать обработчик шаблонов
$template = new PHPTAL('my_template_file.xhtml');

// класс Person
class Person {
    public $name;
    public $phone;

    function Person($name, $phone) {
        $this->name = $name;
        $this->phone = $phone;
    }
}

// Создаем массив объектов для тестирования
$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("quz", "05-321-378-654");

// Передаем массив данных обработчику шаблонов
$template->title = 'Я Заголовок';
$template->people = $people;

// Выполняем обработку шаблона
try {
    echo $template->execute();
}
catch (Exception $e){
    echo $e;
}
?>

Выполнив такой PHP скрипт, Вы получите что-то вроде этого:

<?xml version="1.0"?>
<html>
  <head>
    <title>Я Заголовок</title>
  </head>
  <body>
    <h1>Я Заголовок</h1>
    <table>
      <thead>
        <tr>
          <th>Имя</th>
          <th>Телефон</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>quz</td>
          <td>05-321-378-654</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

PHPTAL не заботится о переносах строки и отступах ни в читаемых, ни в генерируемых файлах. Чтобы получить красивый HTML (с переносами и правильными отступами), можно использовать HTML Tidy на этапе постпроцессинга.

Атрибутивный Язык Шаблонов TAL


Эта секция описывает TAL и его расширения. Эта секция предназначена, главным образом, для разработчиков шаблонов. Но окажется полезной и для тех, кому предстоит интеграторовать шаблоны с PHP.

Приоритет атрибутов

Первым делом запомните: порядок записи атрибутов не имеет никакого влияния на порядок их обработки.

Рассмотрим пример:

<span tal:define="usersList application/listUsers"
      tal:condition="somecondition"
      tal:repeat="user usersList"
></span>

абсолютно идентична такой:

<span tal:repeat="user usersList"
      tal:condition="somecondition"
      tal:define="usersList application/listUsers"
></span>

Приоритет обработки атрибутов, например, tal:define и tal:condition, не будет зависеть от того, в каком порядке они записаны внутри тега SPAN. Порядок обработки определяется спецификацией TAL:

  1. define

  2. condition

  3. repeat

  4. content или replace

  5. attributes

  6. omit-tag

Пространство имен TAL

tal:define

Этот атрибут объявляет одну или более переменных, которые затем могут быть использованы в шаблоне.

Объявление переменной, содержащей длинный путь:

<span tal:define="global destname path/to/existing/variable" />

Объявление строки внутри шаблона:

<span tal:define="global destname string:some string" />

Объявление нескольких переменных одновременно:

<span tal:define="global fname string:paul; lname string:dupond" />

Объявление переменной с использованием другой переменной:

<span tal:define="global hello string:hello $fname welcome on this page" />

И маленький трюк с использованием буфера вывода:

<span tal:define="global hello">hello ${fname} welcome on this page</span>

tal:define можно и удобно использовать вместе с другими атрибутами, но он всегда будет обработан первым.

В предпоследнем примере (объявление переменной с использованием другой переменной) тег span не будет отображен, т.к. он и сам пустой, и не содержит tal атрибутов, которые бы назначели его содержание. Даже в последнем примере текст из span не будет показан, т.к. переменная 'hello' его "съедает" (подменяет своим пустым значением)

Но вот другой пример:

<span tal:define="hello string:hello ${fname} welcome on this page"
      tal:content="hello"
/>

Здесь переменная 'hello' будет установлена, а ее содержимое попадет в тег span

однако, следующий пример неверный (точнее, не отобразит никакого содержания внутри span), т.к. tal:define определит содержание переменной 'hello' перед тем, как tal:content использует ее для определения того, что должно быть внутри элемента span. Чтобы не было указано внутри span в шаблоне, это будет проигнорированно (заменено значением переменной 'hello'). Но в нашем случае hello не будет определено, и поэтому возникнет исключение (exception).

<span tal:define="hello" tal:content="hello">
  hello ${fname} welcome on this page
</span>

Кстати, в этом примере мы не использовали слово 'global' перед определением переменной. В PHPTAL переменные бывают глобальными и локальными.

Глобальная переменная будет доступна во всем шаблоне и в вызываемых из него макросов.

<span tal:define="global hello string:hello world"/>
<p tal:content="hello"/>

Локальная же переменная видна только внутри тега, в котором она определена:

<span tal:define="hello string:hello world"/>
<p tal:content="hello"/> <!-- вызовет ошибку "неопределенной переменной" -->

tal:condition

Тег и его содержимое будет отображено только тогда, когда условие выполняется.

<span tal:condition="identified"> Приветствуем, наш любимый посетитель! </span>
<span tal:condition="not: identified">
  Пожалуйста, авторизуйтесь, чтобы получить доступ к этой странице
</span>

Если программисты PHP скрипта не предоставили Вам (разработчику шаблонов) нужные методы и переменные в передаваемых шаблону данных, Вы можете сами выполнить необходимый PHP код:

<span tal:comment="отобразить, только если в корзине больше 5-и позиций"
      tal:condition="php: cart.countItems() GT 5"></span>

Частое использование инструкций <a0>PHP</a0> в шаблонах привело бы к тому, что логика скрипта была бы разбросана между PHP файлами и шаблонами, что уводит нас от изначальной идеи разделения кода и вида. Поэтому желательно обеспечить разработчиков шаблонов всеми нужными булевыми значениями и методами, например:

<span tal:condition="cart/hasMoreThanFiveItems"></span>
<span tal:condition="fullfillNumerousItems"></span>

tal:repeat

Этот атрибут служит для циклов над перечисляемыми данными: массивами, ассоциативными массивами и перечисляемыми объектами в PHP5

Атрибут repeat поочередно пробегает по всем значениеям указанного ресурса (например, массива).

<tr tal:repeat="item some/result">
  <td tal:content="item">текст, который заменится значением из item</td>
</tr>

Внутри цикла можно использовать специальные ключевые слова repeat/*, чтобы получить доступ к информации об состоянии цикле (и всех внешних циклов).

Вот список допустимых ключевых слов:

  • repeat/item/key : возвращает ключ элемента item в ассоциативном массиве some/result (если ассоциативности нету, то просто возвращает индекс этого элемента)

  • repeat/item/index : возвращает индекс элемента item (от 0 до count-1)

  • repeat/item/number : возвращает порядковый номер элемента item (от 1 до count)

  • repeat/item/even : возвращает true, если индекс четный

  • repeat/item/odd : возвращает true, если индекс нечетный

  • repeat/item/start : возвращает true, если элемент является первым

  • repeat/item/end : возвращает true, если элемент является последним

  • repeat/item/length : возвращает количество элементов в some/result

в этих примерах, вместо названия "item" нужно использовать то же название, что и в выражении tal:repeat (tal:repeat="item some/result").

Одно из самых распространненных применений атрибута tal:repeat - отображение разультатов запроса к базе данных. Следующий пример сработает только в том случае, если playersRanking является объектом, реализующим интерфейс PHP Iterator:

<table>
  <thead>
    <tr>
      <th>Позиция</th>
      <th>Игрок</th>
      <th>Очки</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>

tal:omit-tag

Этот атрибут заставит парсер PHPTAL проигнорировать открывающий и закрывающий теги элемента и вывести только его содержимое.

<span tal:omit-tag="condition">
  если условие condition верно, то только ЭТОТ текст будет показан (открывающий и закрывающий теги span будут упущены)
</span>

В результате получим:

если условие condition верно, то только ЭТОТ текст будет показан (открывающий и закрывающий теги span будут упущены)

Этот атрибут полезен, чтобы создать "опциональный" элемент. Например, спрятать ссылку, если какое-то условие не выполняется.

Если Вам нужен элемент, теги которого никогда не будут выведены, используйте tal:block

<tal:block tal:repeat="x php:range(1,10)">только этот текст будет выведен; 10 раз</tal:block>

tal:replace

Используя этот атрибут, можно заменить весь элемент целиком (включая открывающий и закрывающий тег) на заданную строку, или на пустую, если значение не задано.

<span tal:replace="string:я - прекрасная строка">
  а я строка плохая!
</span>

В результате получим:

я - прекрасная строка

tal:replace удобно использовать для создания примеров в шаблоне, которые должны быть игнорированы при его обработке перед отображением.

<table>
  <tr tal:repeat="item myresult">
    <td tal:content="item">значение item</td>
  </tr>
  <tr tal:replace="">
    <td>Пример 1</td>
  </tr>
  <tr tal:replace="">
    <td>Пример 2</td>
  </tr>
</table>

tal:content

Значение этого атрибута станет текстом внутри тега.

<span tal:define="myvar string:моя строка"/>
<span tal:content="myvar">этот текст будет заменен</span>

В результате получим:

<span>моя строка</span>

tal:attributes

С помощью tal:attributes можно устанавливать на лету значения атрибутов тега.

<a href="http://www.foo.com" title="some foo link"
   tal:attributes="href somelink/href; title somelink/title"
  tal:content="somelink/text"
>какая-нибудь ссылка</a>

Для примера, пусть значение 'somelink' будет таковым:

$somelink->href = "http://www.google.com";
$somelink->title = "google search engine";
$somelink->text = "the google search engine";

В результате получим:

<a href="http://www.google.com"
title="google search engine">the google search engine</a>

Точка с запятой (;) разделяет несколько атрибутов. Чтобы записать точку с запятой как часть значения, необходимо ее продублировать (;;).

Вот пример, где tal:attributes используется совместно с tal:repeat:

<tr tal:repeat="ranking playerRankings"
    tal:attributes="class php: repeat.ranking.odd ? 'odd' : NULL"></tr>

Модификатор php: будет описан ниже. Итак, рассмотрим наш пример. Если рядок нечетный, тогда для tr будет использован атрибут class="odd". Для четных же рядков атрибут class вообще не будет установлен.

"condition ? then : else" является специальным выражением PHP; хотя его польза имеет место быть во многих случаях, лучше попытаться избежать его использувания.

Правильнее было бы попросить PHP программиста обеспечить дизайнера шаблонов специальными модификаторами (см. PHP интеграция / пользовательские модификаторы), которые могли бы использоваться примерно так:

<tr tal:repeat="ranking playerRankings"
    tal:attributes="class css-odd:repeat/ranking/odd"></tr>

Модификатор возвратит "odd", если repeat/ranking/odd равен true, а иначе false.

Опциональные атрибуты

Задавая значение, можно использовать так называемые альтернативные значения, разделяя их знаком |. Если первое значение не определено, парсер попытается подставить следующее в цепочке, если оно тоже не определено - следующе, и т.д. При использовании альтернативных значений, в tal:attributes можно использовать значение nothing (или NULL в выражении php) как последнее альтернативное значение. Тогда атрибут не будет добавлен тегу, если значение для атрибута не определено. Это позволит избежать "пустых" атрибутов:

… tal:attributes="title object/tooltip | nothing"> 

XHTML атрибуты selected, checked и другие подобные будут также обработаны правильно.

<input type="checkbox" tal:attributes="checked object/isChecked"/>

Не забывайте, что XHTML регистро-зависим, поэтому запись SELECTED ошибочна в XHTML. Правильная запись - selected.

tal:on-error

Значение этого атрибута подставится в тег, если тег содержит внутри себя ошибку (например, неверно заданный путь, или исключение, вызванное PHP во время генерации тега).

<span tal:on-error="string:Имя пользователя не определено"
      tal:content="user/name">этот текст заменится на имя пользователя</span>

Если доступ к 'name' или 'user' вызовет ошибку, то на месте тега будет показано сообщение об ошибке.

Этот механим работает для любой вложенности тегов:

<span tal:on-error="string:где-то возникла ошибка">
  <span tal:content="user/firstname"/>
  <span tal:content="user/lastname"/>
  <span metal:use-macro="userMenu" />
</span>

Пространство имен METAL

METAL обозначает 'Macro Extension for TAL' (расширение TAL для макросов). Это пространство имен реализовано в PHPTAL и дает возможность разработчикам создавать и вызывать XML/XHTML макросы в своих шаблонах.

metal:define-macro

Этот атрибут объявляет макрос. По своей сути макросы это обычные шаблоны, которые могут быть многократно вставлены в другие шаблоны.

<div metal:define-macro="main_menu">
  <ul>
    <li><a href="/">Главная</a></li>
    <li><a href="/products">Продукция</a></li>
    <li><a href="/contact">Контакты</a></li>
  </ul>

  <div>
    Последнее обновление:
    <span tal:content="mdate">дата последнего обновления</span>
  </div>
</div>

Макрос наследует доступные переменные из вызывающего шаблона. В нашем примере значение переменной 'mdate' нужно объявить в шаблоне, в котором используют этот макрос.

metal:use-macro

Этот атрибут вызывает макрос и включает результат его выполнения в текущий шаблон в то место, откуда он вызван

<span
  tal:comment="шаблон main_menu требует переменную 'mdate'"
  tal:define="mdate page/last_modified"
  metal:use-macro="main_menu"
/>

Можно даже вызвать макрос из другого файла, просто дописав путь к нему:

<span metal:use-macro="site_macros.xhtml/main_menu"/>

При этом можно использовать всю гибкость подстановок переменных:

<span metal:use-macro="${design}/site_macros.xhtml/main_menu"/>

Макрос может вызывать самого себя. Таким образом, можно использовать рекурсию для вывода массивов:

        <ul metal:define-macro="show-list">
            <li tal:repeat="item list">
                <tal:block tal:condition="php:is_array(item)" tal:define="list item" metal:use-macro="show-list" />
                <tal:block tal:condition="php:!is_array(item)" tal:content="item" />
            </li>
        </ul>
        

metal:define-slot

Этот атрибут должен быть объявлен внутри тега, объявляющего макрос (metal:define-macro).

Слоты могут заменяться вызывающим шаблоном на динамически генерируемый XML/XHTML.

Cлоты помогают разнообразить страницы. Если макрос представить как целую страницу, то слоты можно предствить как секции, варьирующиеся в зависимости от, например, URL. К примеру, на той же главной странице сайта слот выводит свежие новости или перечень действий, доступных пользователю после его авторизации.

<span metal:define-slot="news_place">
  <table>
    <tr tal:repeat="item php:latestNews()">
      <td tal:content="item/value">news description</td>
    </tr>
  </table>
</span>

В этом примере мы определяем секцию под названием 'news_place', содержимое которой может быть подменено вызывающим шаблоном. Ниже мы рассмотрим, как использовать слоты, ссылаясь на этот пример.

metal:fill-slot

Этот атрибут может быть использован только внутри элемента с metal:use-macro.

Он заставит PHPTAL подменить содержимое объявленного раньше слота.

<span tal:condition="logged" metal:fill-slot="news_place">
  <h2>Меню пользователя</h2>
  <ul>
    <li><a href="/user/action/inbox">входящие</a></li>
    <li><a href="/user/action/new">новые письма</a></li>
    <li><a href="/user/action/disconnect">выйти</a></li>
  </ul>
</span>

Таким образом, слоты позволяют с легкостью разнообразить и усложнить шаблоны с помощью простой технологии push.

Пространство имен I18N

К сведению: 'i18n' является акронимом (сокращением) от слова 'internationalization' (интернационализация). Это пространство имен служит для создания многоязыковых шаблонов. С помощью специальных атрибутов можно указывать текст, который должен быть переведен в процессе обработки шаблона.

i18n:translate

Этот атрибут определяет текст, который должен быть переведен с помощью системы перевода, встроенной в PHPTAL.

<div i18n:translate="string:welcome_message">Добро пожаловать!</div>

В вышеприведенном примере PHPTAL будет искать перевод с ключем 'welcome_message' и заменит содержимое тега эквивалентным переводом.

<div i18n:translate="">Добро пожаловать!</div>

А это другой случай: ключ для перевода не указан, поэтому PHPTAL воспользуется содержимым 'Добро пожаловать!' как ключем для перевода. Таким образом, будет подставлен эквивалентный перевод для ключа 'Добро пожаловать!'.

Если же перевода с таким ключем не найдено, то в качестве перевода будет использован сам ключ. Именно поэтому лучше использовать читабельные сообщения в качестве ключей вместо непонятных сокращений.

Мало того, ключ может задаваться переменной:

<div tal:define="welcome random_welcome_message">
  <div i18n:translate="welcome"></div>
</div>

i18n:attributes

Задает, какие атрибуты должны быть переведены. Пары значений Атрибут-Ключ должны быть перечислены через точку с запятой:

<img i18n:attributes="alt 'picture alternative text';title thetitle" alt="Picture" title="${thetitle}" />

i18n:name

В сам перевод можно динамически посдтавлять значения переменных. Для этого нужно назначить так называемые "переменные перевода", то есть, необходимо определить, какую переменную в переводе на какую реальную переменную (доступную в шаблоне) необходимо заменить. Для того, чтобы задать такое соответствие, используйте i18n:name

В строке перевода можно использовать запись ${xxx}, где "xxx" - название "переменной перевода", которая должна заменяться динамически на реальную переменную шаблона.

Соответствующее значение будет подставлено в тег и его содержимое. Если же Вы не хотите, чтобы значение было помещено в теги, используйте tal:replace вместо tal:content. Атрибут tal:omit-tag пригодится, если значение представляет собой конкатенацию строк.

<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 -->

Пример использования i18n:

<div i18n:translate="">
  Добро пожаловать, <span i18n:name="user" tal:replace="user/name"/>,
  у Вас <span i18n:name="mails" tal:replace="user/nbrMails"/>
  непрочитанных писем.
</div>

Ключ перевода в этом примере будет следующий:

"Добро пожаловать, ${user}, у Вас ${mails} непрочитанных писем."

В подставляемом переводе, PHPTAL заменит ${user} на ${user/name}, а ${mails} на ${user/nbrMails}.

Более подробно о связке I18N с PHPTAL можно прочитать в разделе PHP этой справки.

XHTML в переводах

По умолчанию предполагается, что переводы содержат только текст и не содержат теги, поэтому PHPTAL экранирует все символы "<".

Начиная с версии 1.1.14, в i18n:translate допускается использования ключевого слова structure, чтобы отменить экранирование и выводить переведенный текст "как есть":

      <div i18n:translate="structure '<b>bold text</b>'" />
      

Даст:

        <div><b>bold text</b></div>
        

Однако, TAL атрибуты внутри переведенных строк будут игнорированы. Конечно, следует помнить, что плохо форматированный XHTML в переводах испортит хорошо форматированный шаблон.

Пространство имен PHPTAL

Эти атрибуты не определены в спецификациях TAL, но бессомненно могут пригодиться при работе с PHPTAL.

phptal:debug

Этот атрибут активирует отладку PHPTAL для тега с этим атрибутом.

Примечание

To debug errors in macros called across templates you need to add phptal:debug in template which defines the macro, not the one which uses it.

The debug mode stores information like filename and source line number in the template, so exceptions thrown by incorrect path access will contain more information about where they where thrown.

<html>
  <head></head>
  <body>
    <div id="menu"></div>
    <div id="leftPane" phptal:debug=""
      tal:comment="похоже, что этот div содержит ошибки,
      посмотрим-ка, где они возникают"></div>
  </body>
</html>

phptal:cache

Этот атрибут позволяет кешировать на диске результат обработки элемента (включая его тег). Готовое кешированное значение будет использовано до тех пор, пока срок действия кеша не истечет.

Примечание

Использование кеша рекомендуется только для элементов со сложными выражениями, вычислениями, макросами из внешних файлов или PHP доступа к БД. Во всех других случаях, некешированные шаблоны будут настолько же быстро обрабатываться, как и кешированные.

В атрибуте необходимо указать, как долго должен сохраниться кеш. Для этого нужно записать число с суффиксом 'd', 'h', 'm' или 's'.

<div class="footer" phptal:cache="3h"></div>

<div> будет заново обработан как минимум раз в 3 часа.

Можно ограничить "видимость" кеша, дописав "per". По умолчанию, кеш видим всем страницам, использующим этот шаблон. Используйте "per url", чтобы заданный элемент кешировался отдельно для разных URL.

<ol id="breadcrumbs" phptal:cache="1d per url"></ol>

<ol> будет кеширован на один день, отдельно для каждой страницы.

Можно указать "per expression", где expression - некое выражение. Тогда отдельные значения в кеше будут генерироваться для каждого разного значения этого выражения (выражение ДОЛЖНО в результате давать строку). Выражение не может использовать переменные, определенные в том же элементе с помощью tal:define.

<ul id="user-info" phptal:cache="25m per object/id"></ul>

<ul> будет кешироваться каждые 25 минут, отдельно для каждого ID объекта.

Принудительное обновление

Вместо того, чтобы очищать кеш, разумно использовать версию или время последних изменений вместе с per. Это повлечет за собой регенерацию кеша каждый раз, когда версия или время правки будут изменены. В этом случае, можно обойтись без очистки кеша.

<div phptal:cache="100d per php:news.id . news.last_modified_date"></div>

Ограничения:

  • Блоки phptal:cache могут быть вложенными, но самый внешний блок будет кешировать все вложенные блоки, несмотря на состояние их актуальности.

  • Нельзя использовать metal:fill-slot внутри элементов с phptal:cache.

phptal:tales

Этот атрибут позволяет изменять "поведение" PHPTALES. По умолчанию, выражения атрибутов интерпретируются в максимальном соответствии с ZPT (Zope Page Templates). Однако, найдутся случаи, когда Вы бы хотели использовать PHP повсюду, то есть Вы бы везде применяли модификатор php:.

Другая проблема относительно PHPTALES состоит в способе, коим PHPTAL интерпретирует пути. Например, myobject/mymethod/property/10/othermethod/hashkey займет относительно много времени на интерпретацию (НО не концентрируйте на этом слишком много внимания; не нужно оптимизировать, пока вы не заметите реальное снижение производительности!)

PHPTAL должен (runtime) проверить, что myobject - это объект; проверить, что 'mymethod' - метод этого объекта (или, быть может, переменная), и затем вызвать его; убедиться, что результат является объектом со свойство; определить, что значение свойства - массив; найти десятый элемент этого массива и определить, что это объект; определить, что othermethod - метод этого объекта (а не переменная), и получить результат его вызова; определить, что результат - снова объект, и затем получить значение для ключа 'hashkey'.

Конечно, мы перегибаем палку в этом примере, и, на самом деле, чаще всего нас такие проблемы не будут волновать, та как этот процесс очень быстр. Но что, если этот очень длинный путь вызывается в большом цикле tal:repeat? Аминь!.. Именно тут phptal:tales приходит на помощь:

<html>
  <body>
    <table phptal:tales="php">
      <tr tal:repeat="myobject document.getChildren()">
        <td
          tal:content="myobject.mymethod().property[10].otherMethod().hashkey"></td>
      </tr>
    </table>
  </body>
</html>

Please note that the above example does the same as:

<html>
  <body>
    <table>
      <tr tal:repeat="myobject php:document.getChildren()">
        <td
          tal:content="php:myobject.mymethod().property[10].otherMethod().hashkey"></td>
      </tr>
    </table>
  </body>
</html>

О модификаторе 'php:' читайте подробнее в соответствующей главе .

tal:block

tal:block будет очень полезен, когда нужно спрятать много элементов с обильным количеством TAL атрибутов. Другими словами, тег <s1>tal:block</s1> - более короткий способ создать обычный HTML тег с атрибутом <s1>tal:omit-tag</s1>

<tal:block define="myvar string:Some value"/>

то же самое, что и:

<span tal:define="myvar string:Some value" tal:omit-tag=""/>

Другой пример:

<tal:block condition="someCondition" repeat="item someRepeat">
  <div metal:use-macro="x"/>
</tal:block>

то же самое, что и:

<div tal:omit-tag=""
     tal:condition="someCondition"
     tal:repeat="item someRepeat">
  <div metal:use-macro="x"/>
</div>

PHPTALES

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.

path:

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"/>

Предупреждение

If you try to read variable that does not exist, PHPTAL will throw an exception. Use exists: to check if variable can be read

Alternative PHP operator syntax

Because '<', '>' and '&' characters are cumbersome to use in XML, PHPTAL provides alternative syntax for PHP operators using these characters, and a few more for consistency.

These operators can be used only in php: expressions.

  • < : LT (less than)

  • > : GT (greater than)

  • <= : LE (less or equal)

  • >= : GE (greater or equal)

  • == : EQ (equal)

  • != : NE (not equal)

  • && : AND

  • || : OR

string:

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 -->

php:

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.

not:

This expression is a boolean one, useful in tal:condition statements.

<span tal:condition="not: logged">not logged</span>

exists:

This expression returns true if the path specified after it exists, and false otherwise. It is analogous to PHP's isset().

Normally using a path which doesn't exist throws an error like "Cannot find variable 'foo' in current scope". Thus, uncertain paths must be checked first:

<span tal:condition="exists:user/preferences" tal:content="user/preferences">
  Use user/preferences here if defined
</span>

Подсказка

In PHPTALES use isset() instead.

default

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.xhtml" 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.

structure

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.

Expression chains

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 | php:get_default_title()" />

PHP Integration


This section is aimed at PHP developers and explains how to use and customize PHPTAL behaviors for simple and advanced usage.

  • PHPTAL: the main PHPTAL class. It is used to load and execute templates.

  • PHPTAL_Filter: filtering template sources and PHPTAL output.

  • PHPTAL_Trigger: handles output of elements with phptal:id.

  • PHPTAL_TranslationService: for replacing the built-in gettext support with your own internationalization system.

Constants

The only constant defined in PHPTAL.php is PHPTAL_VERSION. It contains version of PHPTAL library installed on your system (in format: X.X.X).

In older versions of there were constants for configuration. They have been removed in favor of methods.

Configuration methods

PHPTAL tries to use best defaults possible and you shouldn't need to change any of the settings.

All of these are methods of the PHPTAL class. set* methods return instance of their class, so you can chain them:

<?php
  echo $phptal->setPhpCodeDestination('/tmp/phptal')->setOutputMode(PHPTAL::XML)->setTemplate('tpl.zpt')->execute();
?>

то же самое, что и:

<?php
  $phptal->setPhpCodeDestination('/tmp/phptal');
  $phptal->setOutputMode(PHPTAL::XML);
  $phptal->setTemplate('tpl.zpt');
  echo $phptal->execute();
?>
  • setEncoding(encoding): Specify what encoding your templates use. The default is UTF-8.

  • setOutputMode(mode): If given PHPTAL::XHTML (the default), will output elements like <img>, <link>, and attribtes like checked, selected according to XHTML specification, including HTML compatibility guidelines. Use PHPTAL::XML if you want to output other XML formats, like Atom or RSS.

  • setTemplateRepository(string_or_array): Specifies where to look for templates. If given a string, it adds it to the list of paths searched. If given array, it replaces the list.

    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.

  • setPhpCodeDestination(path): To tell PHPTAL where to store its intermediate (temporary) PHP files. By default it uses directory given by PHP's sys_get_tmp_dir(), which usually is '/tmp/' directory.

  • setPhpCodeExtension(string): What filename extension should be used for intermediate PHP files. The default is php and frankly, there's no reason to change it.

  • setCacheLifetime(num_days): Maximum number of days intermediate files and fragments cached with phptal:cache should be kept.

  • setForceReparse(boolean): forces reparsing of all templates all the time. This slows down PHPTAL very much, it should be used only for testing and debugging. Never enable this on production servers.

There are other set* methods for filters, internationalization, etc. They have been described in other sections of this manual.

class PHPTAL

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.xhtml');

// 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.xhtml');

?>

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 identifier (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();

// If you specify where the source comes from (second argument to setSource),
// PHPTAL will be able to generate more helpful error messages.
$tpl->setSource($src, __FILE__);
$tpl->title = 'this is my title';
try {
    echo $tpl->execute();
}
catch (Exception $e){
    echo $e;
}

?>

interface PHPTAL_Filter

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.xhtml');
$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.xhtml');
$tpl->setPostFilter($myFilter);
echo $tpl->execute();
?>

interface PHPTAL_Trigger

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.xhtml');

// 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…

interface PHPTAL_TranslationService

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:

method setLanguage()

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;
}
?>

method useDomain($domain)

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.

method setVar($key,$value)

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();
}
?>

method translate($key)

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;
    }
    
}
?>

method setEncoding($encoding)

PHPTAL class calls this method to inform your translation service what encoding is used by the template. translate() method should return strings in that encoding. If you always use the same encoding for templates and translation files (i.e. UTF-8), you can leave this method empty.

Working with gettext

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";
}

Creating the translation directory structure

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/

Portable Object files

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

Translation Domain

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.

Using Translator in PHP

<?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.xhtml');

    // tell PHPTAL to use our translator
    $tpl->setTranslator($tr);

    // output translated template
    echo $tpl->execute();
}
catch (Exception $e){
    echo $e;
}

If you need to translate some other text, that is not in the template (e.g. plaintext e-mail message), you can reuse PHPTAL's translator:

$tr = $tpl->getTranslator();

$subject = $tr->translate("Registration information");

$tr->setVar("user",$username);
$message = $tr->translate("Dear ${user}, thanks for registering!");

mail($email, $subject, $message);

If you're using PHPTAL's standard gettext translator, you can use gettext() too.

Variable interpolation

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.

Creating custom expression modifiers

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 behavior 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 uses phptal_tales() function to generate the
// PHP code that will return the value of the modifier argument.
//
// in the example:
//
//      money: path/to/my/amount
//
// 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($src, $nothrow).')';
}

Note for system administrators


PHPTAL functions by generating PHP files from the template's logic, this means that it needs a directory to store those generated files so they can be parsed by the PHP interpreter.

By default PHPTAL will use the system's temp directory (via PHP's sys_get_temp_dir() function if available) or will try to guess where it should be, /tmp on Unix like systems and c:\windows\temp on Microsoft ones, to store the compiled templates. The default destination can be changed to your liking by calling setPhpCodeDestination() method with the appropriate path. Be it the system's temp directory or a custom one, it needs to have its permissions setup as to allow the PHP running process (the Apache user if using mod_php or the cgi/fastcgi user otherwise) to create and update files in that directory.

PHPTAL creates one file for each different template file and one file for each tag if using phptal:cache. It doesn't create separate files for macros (which are simply compiled as PHP functions inside the compiled template file). These files are automatically cleaned up once in a while, more specifically, each time a template is compiled there is random probability, controlled by setCachePurgeFrequency() method, which will just delete files older than set by setCacheLifetime() method.

Alternatively you can also schedule the deletion of old/unused files by running this from an Unix-like shell (e.g. in a cron job):

find /tmp/ -name tpl_\* \( -atime +1 -o -mtime +14 \) -delete

Useful links


  • ZPT Zope Page Template front page,

  • TAL the Template Attribute Language page,

  • METAL is the Macro Expansion of TAL,

  • TALES the TAL Expression Syntax.

Greetings


Big thanks goes to:

  • ZPT team, who made these useful specifications,

  • The PHPTAL community for their support, help and reports,

  • Jean-Michel Hiver, who 'forced' me to look at them,

  • Olivier Parisy, the first enthusiastic PHPTAL user and bug finder,