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

09/06/2015 0 symfony, шаблон, генератор шаблонів, шаблон Twig, шаблон PHP

Як вам уже відомо, контролер відповідає за обробку кожного запиту, що надходить до додатку Symfony. Насправді контролер всього лиш перекидає виконання абсолютної більшості складних задач до іншого місця для того, аби була змога протестувати код та повторно його використати у майбутньому. Коли контролеру потрібно згенерувати HTML, CSS чи будь-який інший контент,  він перекладає виконання даної задачі на генератор шаблонів. У даному розділі ви навчитеся писати функціональні шаблони для повернення контенту користувачеві, генерації текстів електронних повідомлень та багато чого цікавого!  Ви навчитеся користуватися ярликами, познайомитеся з раціональними способами розширення шаблонів та можливістю повторного використання коду шаблонів.

! Як використовувати шаблони, ми уже описували в розділі “Контролер”, підрозділі “Рендеринг шаблонів”.

Шаблони

Шаблон - це всього лише текстовий файл, що може генерувати будь-який текстовий формат (HTML, XML, CSV, LaTeX ...). Проте найбільш звичним типом є, ймовірно, шаблон PHP - текстовий файл, який аналізується через PHP та містить як простий текст, так і PHP код:

<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1><?php echo $page_title ?></h1>

        <ul id="navigation">
            <?php foreach ($navigation as $item): ?>
                <li>
                    <a href="<?php echo $item->getHref() ?>">
                        <?php echo $item->getCaption() ?>
                    </a>
                </li>
            <?php endforeach ?>
        </ul>
    </body>
</html>

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

<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1>{{ page_title }}</h1>

        <ul id="navigation">
            {% for item in navigation %}
                <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
            {% endfor %}
        </ul>
    </body>
</html>

Twig визначає три типи особливого синтаксису:

      {{ ... }}

"Сказати щось": відображає у шаблоні змінну чи результати виконання.

      {% ... %}

"Зробити щось": тег, який керує логікою самого шаблону; він використовується для виконання таких виразів, як, скажімо for-цикли.

      {# ... #}

"Прокоментувати щось": це аналог синтаксису PHP /* comment */. Його використовують, коли потрібно надати коментар на один чи кілька рядків. Зміст самих коментарів не відображається на сторінках.  

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

{{ title|upper }}

Twig має у своєму арсеналі довжелезний список тегів (tags) та фільтрів (filters), і всі вони доступні за замовчуванням. За потреби ви навіть можете додати до Twig свої розширення.

! Зареєструватись у додатку Twig дуже просто. Власне, так само просто буде і створити новий сервіс та додати до нього тег twig.extension.

Прочитавши документацію, ви згодом відкриєте для себе, що Twig підтримує функції, тож додати до нього якісь інші не складе особливих труднощів. Для прикладу, наступна функція містить стандартний тег for і функцію cycle і виводить десять тегів div зі змінними класами odd, even:

{% for i in 0..10 %}
    <div class="{{ cycle(['odd', 'even'], i) }}">
      <!-- some HTML here -->
    </div>
{% endfor %}

У даному розділі ми будемо надавати зразки шаблонів як для Twig, так і для PHP.

! Якщо ви все ж відмовитеся від використання Twig та вимкнете його, вам доведеться створити власну систему обробки виняткових ситуацій (exception handler) через подію в kernel.exception.

! Чому саме Twig? 

Шаблони Twig вважаються простими, тому вони не оброблять тегів PHP. Причина криється у дизайні: система шаблонів Twig була розроблена радше для створення презентацій, аніж для відображення логіки програмування. Чим більше ви будете використовувати у своїй роботі Twig, тим більше ви його цінуватимете та тим більше користі вам приноситиме робота з ним. Ну і, звісно, всесвітня популярність у колах веб-дизайнерів гарантується!

Крім того, Twig більш функціональний та може робити те, що недосяжне для PHP, а саме здійснення контролю пробілів, “пісочниці” (sandboxing), автоматичні виведення тексту у форматі HTML (HTML escaping), ручні виведення контексту, а також включення користувацьких функцій і фільтрів, що будуть стосуватися лише шаблонів. Twig також має незначні, проте дуже корисні функції, що значно спрощують написання шаблонів та роблять їх більш стислими. Наступний приклад якраз поєднує цикл із логічним твердженням if:

<ul>
    {% for user in users if user.active %}
        <li>{{ user.username }}</li>
    {% else %}
        <li>No users found</li>
    {% endfor %}
</ul>

Кешування шаблонів у Twig

Twig насправді швидкий. Кожен шаблон Twig компілюється до первинного PHP класу, який відображається під час запуску. Компільовані класи розміщуються в директорії app/cache/{environment}/twig (де {environment} є середовищем, наприклад, dev чи prod) та в певних випадках приносять значну користь при знешкодженні багів (debugging). Більше інформації про середовища можна знайти в розділі “Створення сторінок в Symfony”, підрозділі “Середовища”.

При увімкненому режимі debug (що є звичним у середовищі dev), шаблон Twig буде автоматично перекомпільовуватися щоразу, як до нього вносяться будь-які зміни. Це означає, що навіть коли ви програмуєте, можна без вагань робити правки у шаблоні Twig та одразу ж бачити ці зміни; зникає потреба у регулярній чистці кешу.

Якщо режим debug вимкнено (що характерно для середовища prod), вам доведеться чистити директорію кешу Twig, аби шаблони Twig відображали зміни. Не забудьте виконувати таку процедуру, коли викладаєте свій додаток на сервер.

Наслідування шаблонів та розміток

Доволі часто шаблони одного і того ж проекту мають спільні елементи (скажімо, заголовки (headers), нижні блоки (footers), бокові меню (sidebars) та ін. У Symfony дана проблема розглядається під таким кутом: один шаблон може декорувати інший. Саме так працюють класи в PHP: наслідування шаблонів дає можливість створювати базовий “макет”-розмітку, що включав би усі спільні елементи вашого сайту - блоки (проведіть паралелі між PHP класами з базовими методами). Дочірній шаблон може розширювати базову розмітку та перевизначати будь-який з її блоків (аналогічно і з PHP підкласом, який перевизначає певні методи батьківського класу).

Для початку, створіть базовий файл-розмітку:

Twig

{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>{% block title %}Test Application{% endblock %}</title>
    </head>
    <body>
        <div id="sidebar">
            {% block sidebar %}
                <ul>
                      <li><a href="/">Home</a></li>
                      <li><a href="/blog">Blog</a></li>
                </ul>
            {% endblock %}
        </div>

        <div id="content">
            {% block body %}{% endblock %}
        </div>
    </body>
</html>

PHP

<!-- app/Resources/views/base.html.php -->
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title><?php $view['slots']->output('title', 'Test Application') ?></title>
    </head>
    <body>
        <div id="sidebar">
            <?php if ($view['slots']->has('sidebar')): ?>
                <?php $view['slots']->output('sidebar') ?>
            <?php else: ?>
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="/blog">Blog</a></li>
                </ul>
            <?php endif ?>
        </div>

        <div id="content">
            <?php $view['slots']->output('body') ?>
        </div>
    </body>
</html>

! Хоча в темі про наслідування шаблонів ми будемо в більшості говорити термінами Twig, загальна філософія валідна і для шаблонів PHP.

Даний шаблон визначає основу - так званий HTML “кістяк” документа, що моделює сторінку із двома простими колонками. У наступному прикладі визначено три зони {% block %}: це title, sidebar та body. Дочірній шаблон може перевизначити кожен з цих блоків або залишити їх налаштуваня по замовчуванню. Даний шаблон можна відображати й напряму. В такому випадку блоки title, sidebar та body будуть відображати значення даного шаблону, приписані йому по замовчуванню.

Дочірній шаблон може мати наступний вигляд:

Twig

{# app/Resources/views/blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}My cool blog posts{% endblock %}

{% block body %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
{% endblock %}

PHP

<!-- app/Resources/views/blog/index.html.php -->
<?php $view->extend('base.html.php') ?>

<?php $view['slots']->set('title', 'My cool blog posts') ?>

<?php $view['slots']->start('body') ?>
    <?php foreach ($blog_entries as $entry): ?>
        <h2><?php echo $entry->getTitle() ?></h2>
        <p><?php echo $entry->getBody() ?></p>
    <?php endforeach ?>
<?php $view['slots']->stop() ?>

! Батьківський шаблон можна визначити через спеціальний рядковий синтаксис (base.html.twig). Даний шлях означає, що шаблон міститься у директорії app/Resources/views вашого проекту. Ви також можете використовувати й логічне ім'я: ::base.html.twig. Усі умови щодо присвоєння імен описані в підрозділі “Назви шаблонів та їх розміщення” нижче.

Тег {% extends %} є ключовим, коли мова заходить про наслідування шаблонів. Він вказує генератору шаблонів, що спочатку слід оцінити базовий шаблон, у якому міститься базова розмітка та уже визначено кілька блоків. Після цього уже відображається дочірній шаблон, і саме на цьому етапі блоки title і body батьківського шаблону успішно замінюються на відповідні блоки дочірнього. В залежності від значення blog_entries ви можете отримати в результаті щось на кшталт:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>My cool blog posts</title>
    </head>
    <body>
        <div id="sidebar">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog">Blog</a></li>
            </ul>
        </div>

        <div id="content">
            <h2>My first post</h2>
            <p>The body of the first post.</p>

            <h2>Another post</h2>
            <p>The body of the second post.</p>
        </div>
    </body>
</html>

Варто зазначити наступне: оскільки дочірній шаблон не визначив блоку sidebar, у ньому відображатиметься значення з батьківського шаблону. Увесь зміст в межах тега {% block %} батьківського шаблону завжди використовується за замовчуванням.

Ви можете використовувати стільки рівнів наслідування, скільки вам заманеться. У наступному підрозділі ми розглянемо типові трирівневі моделі наслідування, а також пояснимо закономірності організації системи шаблонів у проекті Symfony.

При роботі з наслідуванням шаблонів варто взяти до уваги кілька наступних порад:

1) якщо ви використовуєте у шаблоні тег {% extends %}, він повинен бути першим з ряду інших тегів цього шаблона;

2) чим більше тегів {% block %} міститься у вашому базовому шаблоні, тим краще. Не забувайте, що дочірні шаблони зовсім не повинні визначати усі блоки батьківського, тому просто створіть якомога більше блоків у базовому шаблоні на пропишіть їм по замовчуванню змістовні значення. Більше блоків у батьківському шаблоні - гнучкіша розмітка;

3) якщо ви впіймаєте себе на думці про те, що дублюєте дані в багатьох шаблонах, швидше за все, це свідчить про необхідність перенесення такого контенту до {% block %} батьківського шаблону. В деяких випадках оптимальним вирішенням цієї проблеми може стати перенесення дубльованого контенту до цілком іншого, нового шаблона та включення цих даних через include (див. детальніше у розділі “Створення та використання шаблонів (частина 2)”, підрозділ ”Включення інших шаблонів”);

4) якщо необхідно отримати контент одного з блоків, що міститься у батьківському шаблоні, використовуйте функцію {{ parent() }}. Це справді корисно робити у тих випадках, коли потрібно додати певну інформацію до змісту батьківського блоку замість повного його перевизначення:

{% block sidebar %}
    <h3>Table of Contents</h3>

    {# ... #}

    {{ parent() }}
{% endblock %}

Назви шаблонів та їх розміщення

За замовчуванням шаблони можуть розміщуватися у двох різних локаціях:

1) app/Resources/views/

Директорія views може містити загальні, базові для усього вашого додатку шаблони (а саме шаблони та розмітки пакета цього ж додатку), а також шаблони, які перевизначають шаблони другорядних пакетів (див. детальніше у розділі “Створення та використання шаблонів (частина 2)”, підрозділ “Перевизначення шаблонів пакета”).

2) шлях/до/пакета/Resources/views/

Кожен другорядний пакет розміщує свої шаблони у директорії Resources/views/ (а також у її піддиректоріях). Тож коли ви плануєте поділитися певним пакетом, слід помістити шаблони у пакет, а не в директорію app/.

Більшість шаблонів, які вам доведеться використовувати, “живуть” у директорії app/Resources/views/. Тому їх шлях відповідатиме саме цій директорії. Для прикладу, аби відобразити/розширити app/Resources/views/base.html.twig, ви будете використовувати шлях base.html.twig, а для відображення/розширення app/Resources/views/blog/index.html.twig потрібно буде використовувати шлях blog/index.html.twig.

Посилання на шаблони у пакеті

Symfony використовує рядковий синтаксис bundle:directory:filename для тих шаблонів, які “живуть” в межах одного пакета. Це дає змогу користуватися кількома типами шаблонів, кожен з яких буде поміщений до іншого спеціально відведеного місця:

1) AcmeBlogBundle:Blog:index.html.twig: даний синтаксис використовується для того, аби вказати на конкретний шаблон для конкретної сторінки. Що ж, власне, означають три частини цього рядка, розділені між собою двокрапкою (:)?

          AcmeBlogBundle: (пакет) шаблон розміщений усередині AcmeBlogBundle (наприклад, src/Acme/BlogBundle);

          Blog: (директорія) вказує на те, що шаблон живе усередині піддиректорії Blog директорії Resources/views;

          index.html.twig: (ім'я файлу) це, власне, і є ім'я файлу index.html.twig.

2) за умови, що AcmeBlogBundle “живе” у src/Acme/BlogBundle, кінцевим шляхом до нашої розмітки буде наступний: src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.

3) AcmeBlogBundle::layout.html.twig: даний синтаксис стосується базового шаблона, який належить до AcmeBlogBundle. Так як у серединці бракує кількох складових директорії (наприклад, Blog), шаблон “живе” за адресою Resources/views/layout.html.twig всередині AcmeBlogBundle. Так-так, усередині рядка є дві двокрапки, і з'являються вони тоді, коли пропущено піддиректорію "контролер".

У підрозділі “Перевизначення шаблонів пакета” розділу “Створення та використання шаблонів (частина 2)” ви з'ясуєте, як саме можна перевизначити кожнісінький шаблон, що розміщений у AcmeBlogBundle, через розміщення шаблона з таким же іменем до директорії app/Resources/AcmeBlogBundle/views/. Це не що інше, як хороша можливість перевизначати шаблони з будь-якого зовнішнього пакета.

! На щастя, синтакс найменування шаблонів не стане вам в новинку: він схожий до умов надання імен, які ми розглядали у розділі “Маршрутизація (частина 2)”, підрозділі “Шаблон найменування контролера”.

Суфікси шаблонів

Кожне ім'я шаблона має два розширення, які визначають формат та генератор для цього ж шаблона.

  Ім'я файлу   Формат   Генератор
  blog/index.html.twig   HTML  Twig
  blog/index.html.php   HTML  PHP
  blog/index.css.twig   CSS  Twig

 

 

 

 

За замовчуванням, будь-який шаблон Symfony написаний або у Twig, або у PHP, і саме остання частина розширення (.twig або .php) вказує на те, який саме генератор використовується. При цьому перша частина розширення (а це, для прикладу, .html, .css та ін.) є кінцевим форматом згенерованого шаблона. На противагу генератору, який визначає закономірності розбирання шаблонів Symfony (parsing), в нашім випадку це всього лиш організаційна тактика, яка використовується тоді, коли один і той же ресурс треба відобразити у форматі HTML (index.html.twig), XML (index.xml.twig) чи в будь-якому іншому форматі. Детальніше про це можна буде почитати у підрозділі “Формати шаблонів” у розділі “Створення та використання шаблонів (частина 2)”.

! Ви можете налаштовувати під себе доступні “генератори” і навіть додавати нові. Детальніше читайте у підрозділі “Конфігурація шаблонів” розділу “Створення та використання шаблонів (частина 2)”, "Створення та використання шаблонів (частина 3)".

Поділитися