Створення та використання шаблонів (частина 2)

16/06/2015 0 symfony, шаблон, шаблон Twig, шаблон PHP, тег, хелпер

Продовження. Початок читайте тут: "Створення та використання шаблонів (частина 1)".

Теги та хелпери

На даному етапі ви вже маєте загальне уявлення про шаблони, закономірності їх назв та те, як можна використовувати їх наслідування. Найважче уже позаду. У даному підрозділі ви дізнаєтеся про величезну групу інструментів, що прийдуть вам на допомогу тоді, коли потрібно буде здійснити найтиповіші завдання по роботі із шаблонами, наприклад, підключення інших шаблонів, встановлення посилань на інші сторінки та додавання зображень.

У Symfony вже інтегровано кілька специфічних Twig тегів та функцій, що полегшують проектувальнику шаблонів роботу. Щодо PHP шаблонізатора варто зазначити, що він надає розширювану систему так званих хелперів (помічників), завдання яких - забезпечувати користувачів корисними функціями при роботі з шаблонами.

Ви уже зустрічалися з кількома вбудованими Twig тегами ({% block %} та {% extends %}), а також із прикладом PHP хелпера ($view['slots']). а з цього моменту ви дещо розширите свої пізнання у цій царині.

Підключення інших шаблонів

Так чи інакше, але потреба у підключенні одного і того ж шаблона чи фрагмента коду одразу на кількох сторінках виникатиме частенько. Для прикладу, у додатку, що містить кілька статей-новин, той код шаблона, що відповідає за відображення статей, можна використати і для сторінки з окремою статтею, і для сторінки зі списком найпопулярніших статей, і для побудови списку найсвіжіших новин.

Коли вам потрібно повторно використати шмат PHP коду, ви, як правило, виносите його до нових PHP класу чи функції. Те ж можна робити і з шаблонами. Перемістивши повторно використовуваний код шаблона в окремий шаблон, ви завжди зможете підключити його до інших шаблонів. Для початку створіть шаблон, який будете використовувати кілька разів.

Twig

{# app/Resources/views/article/article_details.html.twig #}
<h2>{{ article.title }}</h2>
<h3 class="byline">by {{ article.authorName }}</h3>

<p>
    {{ article.body }}
</p>

PHP

<!-- app/Resources/views/article/article_details.html.php -->
<h2><?php echo $article->getTitle() ?></h2>
<h3 class="byline">by <?php echo $article->getAuthorName() ?></h3>

<p>
    <?php echo $article->getBody() ?>
</p>

Підключити даний шаблон до іншого зовсім неважко:

Twig

{# app/Resources/views/article/list.html.twig #}
{% extends 'layout.html.twig' %}

{% block body %}
    <h1>Recent Articles<h1>

    {% for article in articles %}
        {{ include('article/article_details.html.twig', { 'article': article }) }}
    {% endfor %}
{% endblock %}

PHP

<!-- app/Resources/article/list.html.php -->
<?php $view->extend('layout.html.php') ?>

<?php $view['slots']->start('body') ?>
    <h1>Recent Articles</h1>

    <?php foreach ($articles as $article): ?>
        <?php echo $view->render(
            'Article/article_details.html.php',
            array('article' => $article)
        ) ?>
    <?php endforeach ?>
<?php $view['slots']->stop() ?>

Шаблон підключаємо, використавши функцію {{ include() }}. Зауважте, що ім'я шаблона відповідає усім загальним правилам надання імен. Шаблон article_details.html.twig використовує підключену нами змінну article. У цьому випадку ми могли б і уникнути таких підключень, так як усі змінні, доступні в list.html.twig, також стають доступними і в article_details.html.twig (допоки ви не скасуєте налаштування with_context

! {'article': article} - це стандартний Twig синтаксис для хеш-карт (тобто, асоціативних масивів). Якби вам потрібно було б передати одразу кілька елементів, синтакси був би орієнтовно таким: {'foo': foo, 'bar': bar}.

Підключення контролерів

У деяких випадках ваша задача передбачатиме щось складніше, аніж підключення простого шаблона. Припустімо, у вашій розмітці є бокова панель, що включає три найновіших статті. Отримання цих трьох статей може включати посилання запитів до бази даних чи здійснення інших непростих завдань, які не можна виконати безпосередньо із шаблона.

Рішення цієї проблеми - просто включити всі результати роботи контролера вашого шаблона. Для початку створіть контролер, який відображатиме певну кількість нещодавно опублікованих статей:

// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;

// ...

class ArticleController extends Controller
{
    public function recentArticlesAction($max = 3)
    {
        // make a database call or other logic
        // to get the "$max" most recent articles
        $articles = ...;

        return $this->render(
            'article/recent_list.html.twig',
            array('articles' => $articles)
        );
    }
}

Шаблон recentList до неможливого простий:

Twig

{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
    <a href="/article/{{ article.slug }}">
        {{ article.title }}
    </a>
{% endfor %}

PHP

<!-- app/Resources/views/article/recent_list.html.php -->
<?php foreach ($articles as $article): ?>
    <a href="/article/<?php echo $article->getSlug() ?>">
        <?php echo $article->getTitle() ?>
    </a>
<?php endforeach ?>

! Зауважте, що URL статті у даному прикладі “захардкоджено” (наприклад, /article/*slug*). Важко назвати таке рішення хорошою практикою. У наступному підрозділі ви дізнаєтеся, як виходити з подібної ситуації правильно.

Аби підключити контролер, вам потрібно зробити на нього посилання, використовуючи стандартний синтаксис для контролерів (а саме bundle:controller:action):

Twig

{# app/Resources/views/base.html.twig #}

{# ... #}
<div id="sidebar">
    {{ render(controller(
        'AcmeArticleBundle:Article:recentArticles',
        { 'max': 3 }
    )) }}
</div>

PHP

<!-- app/Resources/views/base.html.php -->

<!-- ... -->
<div id="sidebar">
    <?php echo $view['actions']->render(
        new \Symfony\Component\HttpKernel\Controller\ControllerReference(
            'AcmeArticleBundle:Article:recentArticles',
            array('max' => 3)
        )
    ) ?>
</div>

Як тільки у вас виникне потреба у певній змінній чи в інформації, якої немає у шаблоні, подумайте над варіантом посиланням на контролер. Контролери швидко працюють та сприяють хорошій організації коду, як, власне, і повторному його використанню. Звісно, як і всі контролери, вони повинні бути максимально “худорлявими”. Тобто, прибирайте з них якомога більше коду (якщо це, звісно, можливо) і перекидайте до сервісів для повторного використання.

Асинхронний контент із hinclude.js

Використовуючи бібліотеку JavaScript hinckude.js, контролери можна включити асинхронно. Оскільки підключений контент сам по собі розміщений на іншій сторінці (чи в іншому контролері), Symfony використовує версію стандартної функції render для того, аби налаштувати теги hinclude:

Twig

{{ render_hinclude(controller('...')) }}
{{ render_hinclude(url('...')) }}

PHP

<?php echo $view['actions']->render(
    new ControllerReference('...'),
    array('renderer' => 'hinclude')
) ?>

<?php echo $view['actions']->render(
    $view['router']->generate('...'),
    array('renderer' => 'hinclude')
) ?>

! Для того, щоб працювала бібліотека hinckude.js, потрібно її включити до вашої сторінки.

! При використанні контролера замість URL потрібно не забути увімкнути можливість конфігурації фрагментів Symfony (fragments):

YAML

# app/config/config.yml
framework:
    # ...
    fragments: { path: /_fragment }

XML

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

    <!-- ... -->
    <framework:config>
        <framework:fragments path="/_fragment" />
    </framework:config>
</container>

PHP

// app/config/config.php
$container->loadFromExtension('framework', array(
    // ...
    'fragments' => array('path' => '/_fragment'),
));

Глобально налаштувати контент по замовчуванню (що виводиться при завантаженнях чи коли вимкнено JavaScript) можна в налаштуваннях вашого додатку:

YAML

# app/config/config.yml
framework:
    # ...
    templating:
        hinclude_default_template: hinclude.html.twig

XML

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

    <!-- ... -->
    <framework:config>
        <framework:templating hinclude-default-template="hinclude.html.twig" />
    </framework:config>
</container>

PHP

// app/config/config.php
$container->loadFromExtension('framework', array(
    // ...
    'templating'      => array(
        'hinclude_default_template' => array(
            'hinclude.html.twig',
        ),
    ),
));

Ви можете визначити шаблони по замовчуванню через функцію render (яка перевизначить будь-який попередньо визначений загальний шаблон по замовчуванню):

Twig

{{ render_hinclude(controller('...'),  {
    'default': 'default/content.html.twig'
}) }}

PHP

<?php echo $view['actions']->render(
    new ControllerReference('...'),
    array(
        'renderer' => 'hinclude',
        'default' => 'default/content.html.twig',
    )
) ?>

А ще ви можете вказати рядок, що відобразиться як контент по замовчуванню:

Twig

{{ render_hinclude(controller('...'), {'default': 'Loading...'}) }}

PHP

<?php echo $view['actions']->render(
    new ControllerReference('...'),
    array(
        'renderer' => 'hinclude',
        'default' => 'Loading...',
    )
) ?>

Посилання на сторінки

Створення посилань на інші сторінки вашого додатку - напевно, одне з найтиповіших завдань при роботі з шаблонами. Замість того, аби намертво фіксувати URL-и у шаблонах, краще використайте Twig функцію path або ж хелпер router у PHP, які й згенерують URL-и на основі налаштувань маршрутизації. Якщо пізніше виникне потреба у зміні URL-а певної сторінки, вам всього лиш потрібно буде змінити конфігурацію маршрутизації; шаблони самі автоматично згенерують новий URL.

Для початку, зробіть посилання на сторінку "_welcome", яка буде доступною за наступними налаштуваннями маршрутизації:

YAML

# app/config/routing.yml
_welcome:
    path:     /
    defaults: { _controller: AppBundle:Welcome:index }

XML

<!-- app/config/routing.yml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="_welcome" path="/">
        <default key="_controller">AppBundle:Welcome:index</default>
    </route>
</routes>

PHP

// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('_welcome', new Route('/', array(
    '_controller' => 'AppBundle:Welcome:index',
)));

return $collection;

Для того, аби перейти на цю сторінку, використайте Twig функцію path та зробіть посилання на маршрут:

Twig

<a href="{{ path('_welcome') }}">Home</a>

PHP

<a href="<?php echo $view['router']->generate('_welcome') ?>">Home</a>

Як і очікувалося, цей рядок створить URL /. А тепер для того, аби отримати складніший маршрут, зробимо наступне:

YAML

# app/config/routing.yml
article_show:
    path:     /article/{slug}
    defaults: { _controller: AppBundle:Article:show }

XML

<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="article_show" path="/article/{slug}">
        <default key="_controller">AppBundle:Article:show</default>
    </route>
</routes>

PHP

// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('article_show', new Route('/article/{slug}', array(
    '_controller' => 'AppBundle:Article:show',
)));

return $collection;

В цьому випадку вам потрібно точно вказати як ім'я маршруту (article_show), так і значення параметра {slug}. Якщо використовуєте цей маршрут, повторно “загляньте” до шаблона recentList з попереднього підрозділу та зробіть правильне посилання на статті:

Twig

{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
    <a href="{{ path('article_show', {'slug': article.slug}) }}">
        {{ article.title }}
    </a>
{% endfor %}

PHP

<!-- app/Resources/views/Article/recent_list.html.php -->
<?php foreach ($articles in $article): ?>
    <a href="<?php echo $view['router']->generate('article_show', array(
        'slug' => $article->getSlug(),
    )) ?>">
        <?php echo $article->getTitle() ?>
    </a>
<?php endforeach ?>

! Абсолютний URL можна згенерувати і з використанням Twig функції url:

<a href="{{ url('_welcome') }}">Home</a>

Те ж можна зробити і з шаблонами PHP, надавши третій аргумент методу generate():

<a href="<?php echo $view['router']->generate(
    '_welcome',
    array(),
    true
) ?>">Home</a>

Посилання на ресурси (assets)

Шаблони також доволі часто посилаються на зображення, JavaScript, сторінки стилів та інші ресурси (assets, надалі просто ресурси) додатку. Звісно, можна намертво зафіксувати до них шлях (скажімо, /images/logo.png), проте з Symfony існує значно динамічніший спосіб, що полягає у використанні Twig функції asset:

Twig

<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />

<link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />

PHP

<img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" alt="Symfony!" />

<link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" type="text/css" />

Основна мета функції asset полягає у тому, аби зробити ваш додаток максимально портативним. Якщо сам додаток міститься в основі хосту (наприклад, http://example.com), в такому випадку відобразиться шлях /images/logo.png. Та якщо додаток міститься у піддиректорії, (скажімо, http://example.com/my_app),  шлях до кожного з ресурсів буде враховувати цю піддиректорію (тобто, отримаємо /my_app/images/logo.png). Функція asset саме для таких речей і створена та визначає закономірності використання додатку, аби відповідно до цього генерувати правильні посилання.

Більше того, при використанні Symfony функції asset можна автоматично додати рядок запиту до ресурсів, аби під час вивантаження на сервер оновлені статичні ресурси не кешувалися. Для прикладу, /images/logo.png може мати і такий вигляд: /images/logo.png?v2. Більш детально про це радимо прочитати за посиланням про налаштування версій ресурсів assets_version.

Якщо вам потрібно встановити версію для для конкретного ресурсу, до бажаної версії можна просто встановити четвертий аргумент (чи аргумент version):

Twig

<img src="{{ asset('images/logo.png', version='3.0') }}" alt="Symfony!" />

PHP

<img src="<?php echo $view['assets']->getUrl(
    'images/logo.png',
    null,
    false,
    '3.0'
) ?>" alt="Symfony!" />

Якщо ви все ж не додасте версію чи вкажете null, по замовчуванню буде використано версію пакета. Якщо ж ви проставите false, для цього ресурсу буде деактивовано URL з версіями.

У випадку, коли для ресурсів потрібно мати абсолютні URL-и, можете встановити третій аргумент true (чи аргумент absolute):

Twig

<img src="{{ asset('images/logo.png', absolute=true) }}" alt="Symfony!" />

PHP

<img src="<?php echo $view['assets']->getUrl(
    'images/logo.png',
    null,
    true
) ?>" alt="Symfony!" />

Продовження читайте за посиланням: "Створення та використання шаблонів "частина 3)".

Поділитися