Как организовать скины в Symfony

от автора

Это короткая заметка о том, как можно организовать использование скинов для брендирования страниц в Twig на примере Symfony. Это решение не привязано к Symfony. По аналогии можно реализовать скины в любом проекте, использующем Twig.

У вас интернет-магазин, онлайн-кинотеатр, афиша мероприятий, каталог телепередач и т.д. В один прекрасный день вам поступает задача по брендированию страницы каталога для привлечения пользователей и повышения продаж под какую-то акцию. Как это сделать, если для движка все продукты в каталоги равнозначны?

Самое простое решение — это захардкодить ID продукта из каталога. Можно добавить условие в шаблон и накладывать на body тег дополнительный CSS класс, по которому потом стилизовать страницу в общих стилях.

{% block body_class -%}     {{ parent () }} product-{{ product.id }} {%- endblock %}

body.product-12345 {    # custom style }

Стилями можно сделать очень многое, особенно если вы используете flex, но стили не всесильны. Иногда возможностей стилей недостаточно для брендирования страницы и необходимо изменить HTML-разметку (вёрстку) страницы, и делается это по аналогии со стилями.

{% if product.id == 12345 %}     {# custom code #} {% else %}     {# original code #} {% endif %}

Решение, конечно, некрасивое, но для единичного случая вполне приемлемо (YAGNI и KISS). Сказано — сделано. Закоммитил, запушил, забыл.

Проходит неделя, две, месяц — и к вам снова прилетает задача по брендированию уже другого товара. Вы делаете по аналогии с прошлым решением, коммитите и забываете. Потом прилетает ещё задача и ещё. Потом до менеджеров продаж или руководства доходит, что брендирование можно продавать за хорошие деньги. Вы и оглянуться не успели, как уже завалены всевозможными задачами по брендированию и правкам оформления. Становится очевидно, что надо что-то менять (DRY).

Лучшим решением в этом случае будет добавить дополнительное поле в сущность с названием скина. Кому как не сущности лучше знать, забрендирована она или нет, и если забрендирована, то как. Добавив поле в сущность, мы убираем всю лишнюю логику из шаблонов и стилей, и остаётся только вопрос организации стилизации по полю скина из сущности.

Рассмотрим страницу продукта с шаблоном product/show.html.twig. Заведем новую структуру папок product/skin/<skin_name>/, где <skin_name> — это значение поля скин у сущности. В качестве скина по умолчанию возьмём default и переместим наш шаблон страницы продукта по соответствующему адресу product/skin/default/show.html.twig. Теперь осталось только поправить контроллер, и можно пользоваться.

public function show(Product $product): Response {     return $this->render(sprintf('product/skin/%s/show.html.twig', $product->skin), [         'product' => $product,     ]); }

Используйте наследование, чтобы не переписывать с нуля каждый раз весь шаблон просмотра продукта, когда нужно поменять только один блок.

{# product/skin/custom_skin/show.html.twig #}  {% extend 'product/skin/default/show.html.twig' %}  {% block some_block %}     {{ parent() }}     {# customise something #} {% endblick %}

Кроме страницы просмотра самого товара, бывают другие с ней связанные. Например, страница отзывов, вопросы и ответы, хронология для фильмов и сериалов, актерский состав и т. д. На них часто есть блоки, общие для всех страниц продукта. В таких случаях обычно создают общий лейаут, который тоже можно загружать из скинов.

{# product/skin/default/show.html.twig #}  {% extend 'product/skin/' ~ product.skin ~ '/layout.html.twig' %}  {# ... #}

{# product/skin/custom_skin/layout.html.twig #}  {% extend 'product/skin/default/layout.html.twig' %}  {# ... #}

В результате мы получаем такую схема расширения:

  • product/skin/<skin_name>/show.html.twig ️▼
  • product/skin/default/show.html.twig ️▼
  • product/skin/<skin_name>/layout.html.twig ️▼
  • product/skin/default/layout.html.twig ️▼

У такого подхода есть один недостаток — вам нужно повторять структуру файлов из скина по умолчанию в каждом новом скине.

  • default
    • layout.html.twig
    • show.html.twig
    • qa.html.twig
    • similar.html.twig
  • first_skin
    • layout.html.twig
    • show.html.twig
    • qa.html.twig
    • similar.html.twig
  • second_skin
    • layout.html.twig
    • show.html.twig
    • qa.html.twig
    • similar.html.twig

Особенно это неприятно, если нужно переопределить только пару строчек в лейауте. Решить эту проблему можно с помощью функций Twig.

public function show(Product $product, Twig $twig): Response {     $template = $twig->resolveTemplate([         sprintf('product/skin/%s/show.html.twig', $product->skin),         'product/skin/default/show.html.twig',     ]);     $content = $template->render([         'product' => $product,     ]);      return new Response($content); }

Метод resolveTemplate() принимает список шаблонов и поочередно пытается их резолвить. Таким образом будет рендерится шаблон страницы товара из скина, если он есть, а если нет — то шаблон по умолчанию. Код получился громоздкий, но ребята из Symfony не хотят его сокращать, поэтому придется писать так или создавать расширение в своем проекте.

Шаблон product/skin/default/show.html.twig расширяет лейаут скина, и мы вынуждены в каждом скине создать как минимум шаблон layout.html.twig. Решить эту проблему можно, передав Twig тегу extend список шаблонов, и тогда extend будет внутри вызывать resolveTemplate() и расширять только существующий шаблон. Это вообще избавит нас от необходимости создавать какой-либо шаблон в папке со скином.

{# product/skin/default/show.html.twig #}  {% extend [     'product/skin/' ~ product.skin ~ '/layout.html.twig',     'product/skin/default/layout.html.twig', ] %}  {# ... #}

Ещё хорошим решением будет вынести стили конкретных скинов в отдельные файлы и подключать только на брендированной странице. Так мы не будем засорять основные стили мусором, который применяется всего на паре страниц. Если кому интересно, то могу в отдельной статье рассказать, как настроить gulp для сборки большого количества скинов вместе и по отдельности. У нас на проекте таких скинов более 300 штук.

ссылка на оригинал статьи https://habr.com/ru/post/488030/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *