Компонент Шаблонизатор (Templating)
Как правило, для отрисовки самих шаблонов с использованием переменных, этот компонент использует какой-либо движок шаблонизатора. Хотя сам компонент позволяет создавать собственные шаблонизаторы, мы с вами узнаем, как правильно интегрировать сторонний движок, например Twig или Smarty. А также настроим загрузку правильного шаблонизатора в зависимости от расширения файла шаблона.
Шаблонизатор с использованием PHP
Использование PHP в шаблонах — это очень распространенная практика. И хотя такой способ имеет свои преимущества, например, не надо изучать новый язык, не надо компилировать файл шаблона, но у него есть и несколько недостатков. К ним относятся:
- Обычно шаблоны на PHP довольно сложны в использовании, особенно когда приходится фильтровать вывод
<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>
- Отсутствие наследования шаблонов, базовых шаблонов, фильтров и расширений
- Довольно сложны в использовании для веб дизайнеров
Компонент Templating имеет в своем составе движок на PHP, который устраняет некоторые из перечисленных недостатков, например, он упрощает фильтрацию вывода, допускает наследование шаблонов и имеет встроенные вспомогательные функции.
Давайте создадим самый простой шаблон, который выводит переменную переданную в контроллер.
Hello <?php echo $name ?>! From a humble PHP template.
Теперь отрисуем шаблон:
<?php
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;
include_once __DIR__. '/vendor/autoload.php';
$loader = new FilesystemLoader(__DIR__.'/views/%name%');
$templating = new PhpEngine(new TemplateNameParser(), $loader);
echo $templating->render('hello.php', array('name' => 'Raul'));
и вот что мы получим на выходе:
Hello Raul! From a humble PHP template.
Давайте разберемся в том, что здесь происходит. Для начала мы создали экземпляр PhpEngine передав все необходимые переменные (объекты реализующие интерфейсы TemplateNameParserInterface и LoaderInterface). Хотя этот код и выглядит устрашающе, ничего сложного в нем нет.
$loader — объект класса FilesystemLoader определяет, где находятся файлы шаблонов — в нашем примере это каталог views
. Объект TemplateNameParser конвертирует строку с адресом шаблона в объект TemplateReference.
И, наконец, выполняем метод render() и отрисовываем шаблон views/hello.php
с переменной name
имеющая значение Raul.
Такая сложная настройка — это цена, которую приходится платить при работе с Symfony2, но как мы увидим далее, отлично спроектированная структура позволяет нам с легкостью добавлять новые шаблонизаторы.
Добавляем новый движок
При добавлении нового движка мы создаем связку между самим движком и компонентом. Чтобы этого добиться надо реализовать три метода из интерфейса EngineInterface.
- render($name, array $parameters = array()): этот метод отвечает за отрисовку шаблона, параметр
name
содержит имя шаблона, аparameters
— переменные шаблона. - exists($name): проверяет существует ли файл шаблона
- supports($name): возвращает значение true если связка движка и компонента может корректно обработать шаблон. Обычно на этом этапе проверяется проверка расширения файла. А также если существует несколько связок, то необходимо выбрать корректную.
Так что, если мы хотим подключить поддержку Twig нам всего лишь следует реализовать следующие методы:
<?php
namespace RaulFraile\Templating;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Twig_Loader_Filesystem;
use Twig_Environment;
class TwigEngine implements EngineInterface
{
protected $environment;
protected $parser;
/**
* Constructor.
*
* @param \Twig_Environment $environment A \Twig_Environment instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param string $dir Templates directory
*/
public function __construct(\Twig_Environment $environment, TemplateNameParserInterface $parser, $dir)
{
$loader = new Twig_Loader_Filesystem($dir);
$this->environment = $environment;
$this->environment->setLoader($loader);
$this->parser = $parser;
}
/**
* Renders a template.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
* @param array $parameters An array of parameters to pass to the template
*
* @return string The evaluated template as a string
*
* @throws \RuntimeException if the template cannot be rendered
*
* @api
*/
public function render($name, array $parameters = array())
{
return $this->environment->loadTemplate((string) $name)->render($parameters);
}
/**
* Returns true if the template exists.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
*
* @return Boolean true if the template exists, false otherwise
*
* @throws \RuntimeException if the engine cannot handle the template name
*
* @api
*/
public function exists($name)
{
$loader = $this->environment->getLoader();
return $loader->exists($name);
}
/**
* Returns true if this class is able to render the given template.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
*
* @return Boolean true if this class supports the given template, false otherwise
*
* @api
*/
public function supports($name)
{
if ($name instanceof \Twig_Template) {
return true;
}
$template = $this->parser->parse($name);
return 'twig' === $template->get('engine');
}
}
Для того, чтобы мы могли использовать внедрение зависимостей в дальнейшем, в только что созданном экземпляре TwigEngine необходимо создать экземпляр Twig_Environment и объект реализующий интерфейс TemplateNameParserInterface, который выдаёт правильное название шаблонизатора основываясь на расширении файла. Так же этот объект принимает адрес каталога в качестве параметра.
Тоже самое касается любого шаблонизатора, поддержку которого мы хотим подключить. Например, класс для шаблонизатора Smarty выглядит следующим образом:
<?php
namespace RaulFraile\Templating;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Smarty;
class SmartyEngine implements EngineInterface
{
protected $smarty;
protected $parser;
/**
* Constructor.
*
* @param Smarty $environment A Smarty instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param string $dir Templates directory
*/
public function __construct(Smarty $smarty, TemplateNameParserInterface $parser, $dir)
{
$this->smarty = $smarty;
$this->smarty->setTemplateDir($dir);
$this->parser = $parser;
}
/**
* Renders a template.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
* @param array $parameters An array of parameters to pass to the template
*
* @return string The evaluated template as a string
*
* @throws \RuntimeException if the template cannot be rendered
*
* @api
*/
public function render($name, array $parameters = array())
{
$this->smarty->assign($parameters);
return $this->smarty->fetch($name);
}
/**
* Returns true if the template exists.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
*
* @return Boolean true if the template exists, false otherwise
*
* @throws \RuntimeException if the engine cannot handle the template name
*
* @api
*/
public function exists($name)
{
return $this->smarty->templateExists($name);
}
/**
* Returns true if this class is able to render the given template.
*
* @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
*
* @return Boolean true if this class supports the given template, false otherwise
*
* @api
*/
public function supports($name)
{
$template = $this->parser->parse($name);
return 'tpl' === $template->get('engine');
}
}
Пример использования в контроллере:
<?php
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\DelegatingEngine;
use RaulFraile\Templating\TwigEngine;
use RaulFraile\Templating\SmartyEngine;
include_once __DIR__. '/vendor/autoload.php';
$templatesDir = __DIR__.'/views/';
$templateNameParser = new TemplateNameParser();
$engines = new DelegatingEngine(array(
new PhpEngine($templateNameParser, new FilesystemLoader($templatesDir . '%name%')),
new TwigEngine(new Twig_Environment(), $templateNameParser, $templatesDir),
new SmartyEngine(new \Smarty(), $templateNameParser, $templatesDir)
));
echo $engines->render('hello.php', array('name' => 'Raul')) . PHP_EOL;
Метод render() обрабатывает все доступные движки и выполняет метод supports() для каждого из них. Как только найдет соответствующий движок, исполняется его метод render().
Замена PHP шаблонизатора: Plates
Если PHP для вас привычнее, но вам не хватает таких аспектов как наследование, базовые шаблоны, плагины и расширения, то есть смысл обратить внимание на движок Plates.
Чтобы подключить Plates в качестве движка по-умолчанию, надо создать новый класс реализующий EngineInterface и принимающий файлы с расширением php в качестве шаблонов.
<?php
namespace RaulFraile\Templating;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use League\Plates;
class PlatesEngine implements EngineInterface
{
protected $engine;
protected $parser;
public function __construct(Plates\Engine $engine, TemplateNameParserInterface $parser, $dir)
{
$this->engine = $engine;
$this->engine->setDirectory($dir);
$this->engine->setFileExtension(null);
$this->parser = $parser;
}
public function render($name, array $parameters = array())
{
$template = new Plates\Template($this->engine);
$template->data($parameters);
return $template->render($name);
}
public function exists($name)
{
return $this->engine->pathExists($name);
}
public function supports($name)
{
$template = $this->parser->parse($name);
return 'php' === $template->get('engine');
}
}
Вызов setFileExtension()
со значением параметра null необходим в нашем примере, чтобы использовать формат имени шаблона hello.php вместо hello.php.php.
Теперь просто заменим объект PhpEngine на PlatesEngine.
<?php
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\DelegatingEngine;
use RaulFraile\Templating\TwigEngine;
use RaulFraile\Templating\SmartyEngine;
use RaulFraile\Templating\PlatesEngine;
use League\Plates;
include_once __DIR__. '/vendor/autoload.php';
$templatesDir = __DIR__.'/views/';
$templateNameParser = new TemplateNameParser();
$engines = new DelegatingEngine(array(
new PlatesEngine(new Plates\Engine(), $templateNameParser, $templatesDir),
new TwigEngine(new Twig_Environment(), $templateNameParser, $templatesDir),
new SmartyEngine(new \Smarty(), $templateNameParser, $templatesDir)
));
echo $engines->render('hello.php', array('name' => 'Raul')) . PHP_EOL;
Сам шаблон не претерпел никаких изменений и теперь нам доступен весь функционал Plates.
Заключение
Компонент Templating сильно выручает, когда надо организовать поддержку нескольких движков, особенно во фреймворках, где активно используется шаблонизация, например статические генераторы блогов и системы плагинов.