System szablonów PHPTAL jako widok w Zend Framework

Artykuł dodany: 28 maja 2009. Ostatnia modyfikacja: 05 listopada 2016.

Stopień trudności (1 - dla początkujących, 5 - dla ekspertów): 4

PHPTAL dla Zend Framework 3 i Zend Expressive

Poniższy artykuł opisuje kod dla frameworka Zenda w wersji 1. Przygotowałem dwa repozytoria które obsługują najnowsze projekty spod stajni Zenda.

- Zend Framework 3
- Zend Expressive

Wprowadzenie

PHPTAL to system szablonów wzorowany na pythonowym ZPT, w całości napisany w PHP5, znacznie lżejszy i wydajniejszy od innych projektów takich jak Smarty czy Open Power Template. Jego największą zaletą jest to, iż generuje poprawnie sformatowany (“well-formed”) dokument a w przypadku popełnienia przez nas błędu (złe zamknięcie tagów) wyświetla stosowną informację. Dodatkowo automatycznie zabezpiecza nas przed atakami XSS. Od pewnego czasu pieczę nad projektem sprawuje Polak, Kornel Lesiński, znany w sieci jako porneL tak więc możemy również liczyć na jego polskie wsparcie. Widać też wyraźny rozwój całego projektu który z każdym wydaniem jest jeszcze lepszy. Zend Framework zawiera w sobie komponent Zend_View jednak jego stosowanie jest średnio przyjemne ale również mało czytelne. W artykule pokażę jak zastąpić domyślny widok PHPTALem. Można również skorzystać z gotowego repozytorium zawierającego wszystkie niezbędne do pracy komponenty.

Dla potrzeb artykułu załóżmy konwencjonalną modułową strukturę katalogową.

/application
    /config (opcjonalnie)
        /app.config.ini #plik konfiguracji
    /(module 1) #rzeczywistą nazwą niech będzie `default`
        /config (opcjonalnie)
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts #tutaj umieszczamy pliki źródłowe, domyślnie z rozszerzeniem .xhtml
                /user
                    /login.xhtml
                    /listusers.xml
                /site.xhtml
    /bootstrap.php #plik odpowiedzialny za startowanie aplikacji
/htdocs (dostępne z poziomu www)
    /images
    /scripts
    /styles
    index.php
/library
    /Zend
    /Phptal #do katalogu wgrywamy zawartość rozpakowanego archiwum/svn PHPTALa
        /PHPTAL
        /PHPTAL.php
    /My
        /Controller
            /Plugin
                /SetPhptalView.php #wczytanie PHPTALa jako plugin kontrolera
        /View
            Phptal.php
        /Browser.php #opis w tekście
/tmp
    /phptal_compiled #pliki przetworzone przez PHPTAL

Dla ułatwienia pliki źródłowe szablonów umieszczamy w katalogu sugerowanym również przez Zend_View. Biblioteki PHPTAL przegrywamy do katalogu /library/Phptal (struktura taka dla zachowania przejrzystości bibliotek). Z poziomu przeglądarki dostępny jest wyłącznie folder /htdocs, reszta poza ogólnie dostępną strukturą katalogową. /My/View/Phptal.php to główny plik odpowiedzialny za połączenie ZF z PHPTALem. Poniżej jego zawartość.

My/View/Phptal.php

<?php

/**
 * PHPTAL as view in Zend Framework
 * Extends Zend_View_Abstract
 * NOTE: this class is supposed to work with Autoloader
 * Original concept by Matthew Ratzloff
 * http://framework.zend.com/wiki/display/ZFPROP/Zend_View_PhpTal+-+Matthew+Ratzloff
 *
 * @category   My
 * @package    My_View
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

class My_View_Phptal extends Zend_View_Abstract {

  /**
   * PHPTAL engine
   *
   * @var PHPTAL
   */
  private $_engine = null;

  /**
   * Ignore HTML/XHTML comments on parsing
   *
   * @var bool
   */
  private $_stripComments = false;

  /**
   * Config options
   * 
   * @var array
   */
  private $_configDirectives = array(
    'compiledDir' => '../tmp/phptal_compiled/',
    'compiledFilesExtension' => 'php',
    'charset' => 'utf-8',
    'gzipCompression' => false,
    'gzipCompressionLevel' => 7
  );

  /**
   * Context variables
   *
   * @var array
   */
  private $_variables = array();

  /**#@+
   * MIME type constants
   * Default: text/plain
   * 1 - Automatic selection: application/xhtml+xml, text/html
   * 2 - application/xhtml+xml
   * 3 - text/html
   * 4 - text/xml
   */
  const MIME_XHTML_AUTO = 1;
  const MIME_XHTML = 2;
  const MIME_HTML = 3;
  const MIME_XML = 4;
  /**#@-*/

  /**
   * Constructor
   *
   * @param array $config
   */
  public function __construct(array $config = array()) {
    ini_set('include_path', ini_get('include_path').PATH_SEPARATOR.'../library/Phptal/');
    $this->_engine = new PHPTAL();

    $this->_loadConfig($config);
    $this->_setCompiledDir();
    $this->setEncoding();
    $this->_engine->setPhpCodeExtension($this->_configDirectives['compiledFilesExtension']);
    $this->_engine->set('helper', $this);
    parent::__construct();
  }

  /**
   * Return the template engine object
   *
   * @return PHPTAL
   */
  public function getEngine() {
    return $this->_engine;
  }

  /**
   * Set template variables
   *
   * @param string $key
   * @param mixed $value
   */
  public function __set($key, $value) {
    $this->assign($key, $value);
  }

  /**
   * Get template variable
   *
   * @param string $key
   * @return null|mixed
   */
  public function __get($key) {
    if ($this->__isset($key)) {
      return $this->_variables[$key];
    }
    return null;
  }

  /**
   * Check if template variable is set
   *
   * @param string $key
   * @return bool
   */
  public function __isset($key) {
    return array_key_exists($key, $this->_variables) and ($key[0] != '_');
  }

  /**
   * Unset template variable
   *
   * @param string $key
   */
  public function __unset($key) {
    if ($this->__isset($key)) {
      unset($this->_variables[$key]);
    }
  }

  /**
   * Clone engine object
   */
  public function __clone() {
    $this->_engine = clone $this->_engine;
  }

  /**
   * Assigns variables to the view script via differing strategies.
   *
   * Zend_View::assign('name', $value) assigns a variable called 'name'
   * with the corresponding $value.
   *
   * Zend_View::assign($array) assigns the array keys as variable
   * names (with the corresponding array values).
   *
   * @see    __set()
   * @param  string|array The assignment strategy to use.
   * @param  mixed (Optional) If assigning a named variable, use this
   * as the value.
   * @return My_View_Phptal Fluent interface
   * @throws Zend_View_Exception if $spec is neither a string nor an array,
   * or if an attempt to set a private or protected member is detected
   */
  public function assign($spec, $value = null) {
    if (is_string($spec)) {
      if ('_' == substr($spec, 0, 1)) {
        throw new Zend_View_Exception('Setting private or protected class members is not allowed', $this);
      }
      $this->_variables[$spec] = $value;
    } elseif (is_array($spec)) {
      $error = false;
      foreach ($spec as $key => $val) {
        if ('_' == substr($key, 0, 1)) {
          $error = true;
          break;
        }
        $this->_variables[$key] = $val;
      }
      if ($error) {
        throw new Zend_View_Exception('Setting private or protected class members is not allowed', $this);
      }
    } else {
      throw new Zend_View_Exception('assign() expects a string or array, received ' . gettype($spec), $this);
    }

    return $this;
  }

  /**
   * Get all context variables
   *
   * @return array
   */
  public function getVars() {
    return $this->_variables;
  }

  /**
   * Clear all context variables
   */
  public function clearVars() {
    $this->_variables = array();
  }

  /**
   * Set Content-Type header
   *
   * @param int $mimeType
   * @param string $encoding
   * @return My_View_Phptal
   */
  public function setContentType($mimeType = null, $encoding = null){
    if (null !== $encoding) {
      $this->setEncoding($encoding);
    }
    $encoding = $this->getEncoding();

    $frontController = Zend_Controller_Front::getInstance();

    if (Zend_Registry::isRegistered('browser')) {
      $browser = Zend_Registry::get('browser');
    } else {
      $browser = new My_Browser();
      Zend_Registry::set('browser', $browser);
    }
    switch ($mimeType) {
      case 1 :
        if ($browser->detectXhtmlMime()) {
          $contentType = 'application/xhtml+xml';
        } else {
          $contentType = 'text/html';
        }
        break;
      case 2 :
        $contentType = 'application/xhtml+xml';
        break;
      case 3 :
        $contentType = 'text/html';
        break;
      case 4 :
        $contentType = 'text/xml';
        break;

      default:
        $contentType = 'text/plain';
        break;
    }
    $frontController->getResponse()->setHeader('Content-Type', ''.$contentType.'; charset='.$encoding, true);
    return $this;
  }

  /**
   * Returns character encoding for output
   *
   * @return string Character encoding
   */
  public function getEncoding() {
    return $this->_engine->getEncoding();
  }

  /**
   * Set character encoding for output.
   * Must be executed before @see setContentType()
   *
   * @param string $encoding Character encoding
   * @return My_View_Phptal
   */
  public function setEncoding($encoding = null) {
    $encoding = (is_null($encoding)) ? $this->_configDirectives['charset'] : $encoding;
    $this->_engine->setEncoding($encoding);
    return $this;
  }

  /**
   * Get output mode (XHTML or XML)
   *
   * @return int Constant value of PHPTAL_XHTML or PHPTAL_XML
   */
  public function getOutputMode() {
    return $this->_engine->getOutputMode();
  }

  /**
   * Set output mode (XHTML or XML)
   *
   * @param int $mode PHPTAL_XHTML or PHPTAL_XML
   * @return My_View_Phptal
   */
  public function setOutputMode($mode) {
    $this->_engine->setOutputMode($mode);
    return $this;
  }

  /**
   * Get whether to ignore HTML comments when parsing
   *
   * @return bool
   */
  public function getStripComments() {
    return $this->_stripComments;
  }

  /**
   * Ignore XML/XHTML comments on parsing
   *
   * @param bool $bool
   * @return My_View_Phptal
   */
  public function setStripComments($bool = true) {
    $this->_engine->stripComments($bool);
    $this->_stripComments = $bool;
    return $this;
  }

  /**
   * Flags whether to ignore intermediate php files and to
   * reparse templates every time (if set to true).
   * Don't use in production - this makes PHPTAL significantly slower.
   * 
   * @param bool $bool Forced reparse state.
   * @return My_View_Phptal
   */
  public function setForceReparse($bool) {
    $this->_engine->setForceReparse($bool);
    return $this;
  }

  /**
   * Get whether to force a reparse or not
   *
   * @return bool
   */
  public function getForceReparse() {
    return $this->_engine->getForceReparse();
  }

  /**
   * Get the path to the PHP generated file
   *
   * @return string PHP generated file path
   */
  public function getCodePath() {
    return $this->_engine->getCodePath();
  }

  /**
   * Get the extension used for PHP files
   *
   * @return string PHP extension
   */
  public function getCodeExtension() {
    $this->_engine->getPhpCodeExtension();
  }

  /**
   * Load config options
   *
   * @param array $config
   */
  private function _loadConfig($config){
    if(!is_array($config)) {
      throw new My_View_Exception('Error loading configuration (must be an array)');
    }
    foreach($config as $_optk=>$_optv){
      if (!array_key_exists($_optk, $this->_configDirectives)) {
      	throw new My_View_Exception("Configuration: Unknown option `$_optk`");
      }
      $this->_configDirectives["$_optk"] = $_optv;
    }
  }

  /**
   * Set directory for compiled files
   */
  private function _setCompiledDir(){
    if (!is_dir($this->_configDirectives['compiledDir'])) {
      throw new My_View_Exception('Specified option `compiledDir` must be a directory');
    }
    if(!is_writable($this->_configDirectives['compiledDir'])) {
    	throw new My_View_Exception('Compiled files directory must be writable');
    }
    $this->_engine->setPhpCodeDestination($this->_configDirectives['compiledDir']);
  }

  /**
   * Assign all variables to the PHPTAL engine
   *
   * @param array $variables Variables to assign
   */
  private function _assignAll(array $variables = array()) {
    foreach ($variables as $key => $value) {
      $this->_engine->set($key, $value);
    }
  }

  /**
   * @see Zend_View_Abstract
   */
  protected function _run() {
    $this->_engine->setTemplate(func_get_arg(0));
    $this->_assignAll($this->_variables);
    try {
      if ($this->_configDirectives['gzipCompression'] &&
        extension_loaded('zlib') &&
        ini_get('zlib.output_compression') == 0 &&
        stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')
      ) {
        $frontController = Zend_Controller_Front::getInstance();
        $frontController->getResponse()->setHeader('Content-Encoding', 'gzip', true);
        $frontController->getResponse()->setHeader('Vary', 'Accept-Encoding', true);
        $buffer = gzencode($this->_engine->execute(), $this->_configDirectives['gzipCompressionLevel'], FORCE_GZIP);
        $frontController->getResponse()->setHeader('Content-Length', strlen($buffer), true);
        echo $buffer;
        unset ($buffer);
        return;
      }
      echo $this->_engine->execute();
    } catch (Zend_View_Exception $e) {
      throw new Zend_View_Exception($e);
    }
  }

}

/**
 * PHPTAL context modifier (helper:)
 *
 * @param string $src
 * @param boolean $nothrow
 * @return string
 */
function phptal_tales_helper($src, $nothrow) {
  $src = 'helper->'.trim($src);
  return PHPTAL_Php_Transformer::transform($src, '$ctx->');
}

Postanowiłem rozszerzyć klasę Zend_View_Abstract gdyż w ten sposób otrzymujemy pełną integrację z innymi komponentami jak Zend_Form. Sama implementacja Zend_View_Interface w niektórych przypadkach jest również bardzo korzystna ponieważ w mniejszym stopniu konsumuje zasoby. Drugiej metody nie będę omawiał ale sprowadza się głównie do przeniesienia kodu z metody _run() do render(). Należy również zadbać o wczytywanie helperów (metody add* z Zend_View_Abstract).

Klasa daje również możliwość gzipowanie zawartości i wysłanie odpowiednich nagłówków HTTP.

Do działania potrzebujemy jeszcze jednej klasy – /My/Browser.php z metody setContentType().

My/Browser.php

<?php
class My_Browser {
  public $clientAcceptXhtml = false;

  /**
   * Check if client can accept MIME XHTML.
   * 
   * @param void
   * @return bool true if client has MIME XHTML support, false if not detected.
   */
  public function detectXhtmlMime(){
    $this->clientAcceptXhtml = false;
    $sAccept = (isset($_SERVER['HTTP_ACCEPT'])) ? $_SERVER['HTTP_ACCEPT'] : '';
    if (preg_match('/application\/xhtml\+xml(?![+a-z])(;q=(0\.\d{1,3}|[01]))?/i', $sAccept, $matches)){
      $xhtmlQ = isset($matches[2])?($matches[2]+0.2):1;
      if (preg_match('/text\/html(;q=(0\d{1,3}|[01]))s?/i', $sAccept, $matches)){
        $htmlQ = isset($matches[2]) ? $matches[2] : 1;
        $this->clientAcceptXhtml = ($xhtmlQ >= $htmlQ);
        return $this->clientAcceptXhtml;
      } else {
        $this->clientAcceptXhtml = true;
        return $this->clientAcceptXhtml;
      }
    }
    return false;
  }
}

Sprawdza ona czy przeglądarka obsługuje typ MIME application/xhtml+xml. Opera i Firefox tak, IE nie obsługuje w związku z czym otrzyma text/html. Jeśli nie rozumiesz o czym mówię przeczytaj mój wcześniejszy artykuł.

Następnym krokiem będzie wskazanie PHPTAL jako widok. Dokonamy tego poprzez rejestrację pluginu. Utwórz plik /library/My/Controller/Plugin/SetPhptalView.php:

My/Controller/Plugin/SetPhptalView.php

<?php

class My_Controller_Plugin_SetPhptalView extends Zend_Controller_Plugin_Abstract {

  public function preDispatch(Zend_Controller_Request_Abstract $request) {
    $config = Zend_Registry::get('config');
    $frontController = Zend_Controller_Front::getInstance();
    $dispatcher = $frontController->getDispatcher();

    $moduleNameUnformatted = ($request->getModuleName()) ?: $dispatcher->getDefaultModule();  
    $moduleDir = $frontController->getModuleDirectory();
    $moduleName = $dispatcher->formatModuleName($moduleNameUnformatted);

    $view = new My_View_Phptal($config->phptal->toArray());
    $view->setContentType(My_View_Phptal::MIME_HTML)
         ->addHelperPath($moduleDir.'/views/helpers', $moduleName.'_View_Helper');
    $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
    $viewRenderer->setView($view)
                 ->setViewSuffix('xhtml');
  }

}

W pluginie automatycznie rejestrujemy ścieżkę do naszych własnych helperów (…/views/helpers), ustawiamy rozszerzenie szablonów na xhtml, oraz wysyłamy do klienta odpowiednie nagłówki (zobacz stałe My_View_Phptal::MIME*).

Następnie w swoim pliku startowym (bootstrap) zarejestruj plugin:

Bootstrap.php

[...]
$frontController = Zend_Controller_Front::getInstance();
$frontController->registerPlugin(new My_Controller_Plugin_SetPhptalView());

W zaprezentowanym kodzie wykorzystywana jest również konfiguracja zarejestrowana w Zend_Registry pod kluczem ‘config’. Przyjmijmy, że korzystamy z Zend_Config_Ini wówczas przyjmie ona postać:

/application/config/app.config.ini

[production]
phptal.compiledDir = "../tmp/phptal_compiled/"
phptal.compiledFilesExtension = "php"
phptal.charset = "UTF-8"
phptal.gzipCompression = true
phptal.gzipCompressionLevel = 7

Bootstrap.php

[...]
$config = new Zend_Config_Ini('../application/config/app.config.ini', 'production');
Zend_Registry::set('config', $config);

Nota dla korzystających z Zend_Application

Zamiast rejestrować My_Controller_Plugin_SetPhptalView jako wtyczkę kontrolera należy stworzyć nowy obiekt – plugin zasobów (resource plugin).

My/Application/Resource/Phptal.php

<?php

class My_Application_Resource_Phptal
      extends Zend_Application_Resource_ResourceAbstract {

  /**
   * View object
   *
   * @var My_View_Phptal
   */
  protected $_view;

    /**
     * Defined by Zend_Application_Resource_Resource
     *
     * @return My_View_Phptal
     */
  public function init() {
    $view = $this->getView();

    $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
    $viewRenderer->setView($view)
                 ->setViewSuffix('xhtml');
    Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
    return $view;
  }

    /**
     * Retrieve view object
     *
     * @return My_View_Phptal
     */
  public function getView() {
    if (null === $this->_view) {
      $frontController = $this->getBootstrap()->getResource('frontController');
      $request = $frontController->getRequest();
      $this->_view = new My_View_Phptal($this->getOptions());
      $this->_view->setContentType(My_View_Phptal::MIME_HTML)
           ->addHelperPath('../application/modules/'.$request->getModuleName().'/views/helpers', $request->getModuleName().'_views_helpers_');
    }
    return $this->_view;
  }
}

Autoloader (Zend/Application/Module/Autoloader.php) automatycznie rejestruje przestrzeń nazw ‘View_Helper’.

Klucze konfiguracyjne dostępne będą dla opcji

[production]
resources.My_Application_Resource_Phptal.compiledDir = APPLICATION_PATH "/../tmp/phptal_compiled/"
resources.My_Application_Resource_Phptal. [...]

Kontroler i plik szablonu

PHPTAL powinien być gotowy do użycia. Przetestujmy. Przyjmijmy że mamy kontroler “user”, akcję “login” oraz wykorzystujemy makra i sloty. Specjalnie daję taki przykład aby pokazać jak świetnie taka koncepcja sprawdza się w Zend Framework. I to bez wykorzystywania dodatkowego kodu php. W sieci można znaleźć przykłady pełnej integracji PHPTAL z Zend_Layout gdzie stosujemy zwykłą metodę szablon główny -> include jednak według mnie neguje to całą ideę PHPTALa.

Zazwyczaj strona posiada jakiś główny plik nazwijmy go /application/modules/default/views/scripts/site.xhtml.

/application/default/views/scripts/site.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      metal:define-macro="html">
<head>
<tal:block metal:define-slot="head"/>
</head>
<body xml:lang="pl" lang="pl">
<div id="content"><tal:block metal:define-slot="content"/></div>
</body>
</html>

Przestrzenie nazw nie są wymagane ale dla poprawności dokumentu warto je dodawać (zostaną automatycznie usunięte z kodu wynikowego). W dokumencie widać również definicję makra “html” oraz slotów “head” i “content”. Wykorzystajmy je z naszym kontrolerem user/login. Utwórz plik /application/default/views/scripts/user/login.xhtml.

/application/default/views/scripts/user/login.xhtml

<tal:block metal:use-macro="../site.xhtml/html">

<tal:block metal:fill-slot="content">
  <tal:block tal:content="structure form">form data</tal:block>

  <span tal:on-error="string:No username defined here"
        tal:content="user/name">the user name here</span>
  <div id="some" tal:content="structure helper:action('helper-test', 'user', null)"></div>
</tal:block>

<tal:block metal:fill-slot="head">
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <link href="stylesheet.css" rel="stylesheet" type="text/css"/>
</tal:block>

</tal:block>

Plik wypełniłem odrobinę losową zawartością aby zaprezentować łączenie z ZF. Zwróć uwagę na konstrukcję helper:action… W tym wypadku jest to mapowanie do helpera Zend_View_Helper_Action aczkolwiek możesz w ten sposób odwoływać się do dowolnych wbudowanych pluginów. Dodatkowo została zarejestrowana ścieżka …/views/helpers w którym to katalogu możesz umieszczać własne klasy. Formularz generujemy poprzez Zend_Form tak więc niezbędne jest użycie słowa “structure” aby PHPTAL wyświetlił kod HTML. Po kodzie widać, że jeżeli tylko chcesz możesz w zależności od kontrolera/akcji wczytywać dowolny plik główny, lub podstawiać różne dane do głównego szablonu. Daje to ogromne możliwości manipulacji dokumentem zwłaszcza w połączeniu z tal:condition.

Dane formularza oraz helper wypełniamy w kontrolerze.

/application/default/controllers/UserController.php

<?php

class UserController extends Zend_Controller_Action {
  public function loginAction(){
    //code from Zend_Form docs
    $form = new Zend_Form();
    $form->setAction('/user/login')
         ->setMethod('post');

    // Create and configure username element:
    $username = $form->createElement('text', 'username');
    $username->addValidator('alnum')
             ->addValidator('regex', false, array('/^[a-z]+/'))
             ->addValidator('stringLength', false, array(6, 20))
             ->setRequired(true)
             ->addFilter('StringToLower');

    // Create and configure password element:
    $password = $form->createElement('password', 'password');
    $password->addValidator('StringLength', false, array(6))
             ->setRequired(true);

    // Add elements to form:
    $form->addElement($username)
         ->addElement($password)
    // use addElement() as a factory to create 'Login' button:
         ->addElement('submit', 'login', array('label' => 'Login'));
    $this->view->form = $form;
    // some todo code
  }

  public function helperTestAction() {
    $this->_helper->viewRenderer->setNoRender();
    echo 'some test data';
  }

Jeżeli formularz został utworzony poprawnie oraz zobaczyłeś tekst ‘some test data’ wszystko działa jak należy.

Zobaczmy jeszcze jak łatwe jest tworzenie plików XML. Do kontrolera możemy dopisać przykładową akcję “listusers”.

/application/default/controllers/UserController.php

public function listusersAction() {
  $this->view->users = $exampleDbTableUsers->fetchAll();
  $this->_helper->viewRenderer->setViewSuffix('xml');
  $this->view->setContentType(My_View_Phptal::MIME_XML);
  $this->view->setOutputMode(PHPTAL::XML);
}

Przekazujemy do PHPTALa zmienną “users” zawierającą jakieś dane o użytkownikach pobrane z bazy. Zmieniamy też rozszerzenie plików na .xml oraz wysyłane nagłówki na ‘text/xml’ (MIME_XML). Tworzymy plik widoku:

/application/default/views/scripts/user/listusers.xml

<?xml version="1.0" encoding="UTF-8"?>
<users>
   <user tal:repeat="user users">
     <id>${user/id}</id>
     <name tal:content="string:text+ ${user/name}"></name>
     <rank tal:content="user/rank"></rank>
   </user>
</users>

Plik jest bardzo czytelny (zaprezentowałem trzy różne metody odwoływania do zmiennych), możemy go podejrzeć w przeglądarce jako typowy xml. To kolejna zaleta PHPTALa.

Podsumowanie

PHPTAL to potężne narzędzie, do tego bardzo elastyczne i mam nadzieję że artykuł ten zachęci niezdecydowanych przynajmniej do pierwszych testów. Jako kontynuację zapraszam do lektury dokumentacji PHPTALa.

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.