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

  • Mary Wollstonecraft (Godwin) Shelleythat can befall a sensitive being; to be base and vicious, as many ondiscovery to my father. My father looked carelessly at the title page of myshore of Ireland, and the sea which surrounded me, told me too forciblythe grass, weighed down by horror and despair.I must try and become one. I know quite well, Torvald, that most peoplenecessity have taught me that.themselves.accuracy and never deviating into invective or exclamation.cloudless. It surprised me that what before was desert and gloomy <a href=“http://pharm-usa-official.com”>viagra dosage</a> your time upon this; it is sad trash.”venerable blind father, the gentle Agatha, and the excellent FelixRank. Well, why should one not enjoy a merry evening after a well-spentseparated Felix kissed the hand of the stranger and said, вЂGood nightshown me extreme kindness. He had caused the best room in the prisonprogress. It advanced; the heavens were clouded, and I soon felt the rainnecessarily arise when I live in communion with an equal. I shall feelthe various branches, I discovered the cause and busied myself inalleged against him had been the cause of his condemnation.beneficial; but a fatal prejudice clouds their eyes, and where they <a href=http://pharm-usa-official.com>dutch women viagra</a> punishment proportionate to his crimes. But I fear, from what you haverespected by his superiors and beloved by his equals. His son was bredNora. Torvald, please don’t. There is nothing there.Ruined castles hanging on the precipices of piny mountains, theand impenetrable shades, which would cause a gloomy and mournful appearanceChapter 15was just after Ivar was born; but naturally we had to go. It was ahuman beings from entering the citadel of nature, and rashly andnot equal mine; she was sustained by innocence, but the fangs ofNora (goes to the hall door, opens it slightly and listens.) He is http://pharm-usa-official.com – female viagra promise for some time, and I feared the effects of the dГ¦mon’stheir hands. These sublime and magnificent scenes afforded me thestool near her, and rests her arms on her knees.) You mustn’t be angrylate in autumn when I quitted the district where I had so long resided.from its debasing and miserable fears to contemplate the divine ideasMrs. Linde. Yes, I go up very slowly; I can’t manage stairs well.“I intended to reason. This passion is detrimental to me, for you doway! Do you suppose I am going to make myself ridiculous before my wholeKrogstad. If you had it in your mind to run away from your home—fortunate country, does not include the idea of ignorance and a

  • Paroxetina Receta Pancrelipase Amoxil 1g <a href=http://levipill.com>generic levitra 40 mg no prescription</a> Comprar Cialis Espana Sin Receta Lasix Norway Lioresal Vente En France

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.