Бэкенд / Подключение процессора шаблонов Twig

Идем дальше по дорожке развития PHP приложения.

Современные веб приложения очень трепетно относятся к организации кода.

За что в первую очередь критиковали PHP, а за то что он по сути не отделяет вёрстку от кода А при росте сложности приложения превращается в лапшу.

Для решения этой проблему решили, что пусть php файлы содержат только код. А разметка пусть где-то отдельно лежит.

Давайте попробуем так сделать.

Создадим новый проект.

то есть две папки public и views. Папка views пустая, а в папке public наша точка входа index.php.

Мы этот проект создаем чисто в целях разобраться. Потом надо будет вернуться к предыдущему. Так что вы его не в коем случае не удаляйте. Вам придется повторить на нем все что мы тут сейчас сделаем.

Переключаемся на новый проект

И так, мы решили, что в php файлов не должно быть верстки. Значит верстку будем пихать в папку views, будем использовать просто html файлы.

Возьмем картинки mermaid.jpg uranus.png, и положим их в папку images

Ну и файлики сделаем

и вот вроде бы как все хорош. Отдельно файлики, отдельно код.

Но теперь возникает другая проблема. А как мне сделать навигацию, да так чтобы поменьше кода писать? Или как мне например передать данные в html файл и чтобы он их обработал, но чтобы php код писать не пришлось.

В общем, для решения этой проблемы были придуманы так называемый Templates engines, то есть шаблонизаторы по-русски.

Помимо того, что шаблонизаторы упрощают написание динамических элементов (как например добавление класс active в прошлой задачке) плюс ко всему они добавляют возможность строить иерархию шаблонов, то есть реализовывать наследование.

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

Twig

Мы будем использовать один из самых популярных шаблонизаторов, а именно Twig https://twig.symfony.com

чтобы начать его использовать, надо его сначала установить. Для установки дополнительных пакетов в php используется специальная утилита composer. Она скачивает дополнительные библиотечки из интернета и кладет их в папку vendor

В общем, тыкаем на Terminal

это запустит нам консольку вместе с настроенными переменными окружения

тут есть важный момент, путь который написан в этой строке должен соответствовать папке в которой лежит ваш проект

если по какой-то причине там какой-то другой путь. То вам надо перейти в консольке в папку с вашим проектом.

Для этого найдите папку с вашим проектом. Скопируйте путь к этой папке

и введите в консольке команду

cd П:\уть\к\папке\с\вашим\проектом

в моём случае будет

cd C:\Users\m\Desktop\php_01

Теперь можно попробовать написать composer и посмотреть, что выведет:

тут информация как библиотечкой управлять, вдруг кому полезна будет

Тут нам важно одно, так как composer по умолчанию ставит пакеты в текущую папку, то в целях безопасности надо обязательно выйти из папки public, если у вас путь оканчивается на public

то вызовете следуюущую команду чтобы подняться на уровень выше:

cd ..

теперь идем на сайт twig https://twig.symfony.com/doc/3.x/intro.html#installation и смотрим как правильно установить twig

там вот что написано:

то есть надо ввести команду.

К сожалению, версия composer, которая идет с laragon, устаревшая, так что придется ее обновить. Идем на сайт https://getcomposer.org/download/, скролим вниз и качаем последнюю версию и сохраняем в папку laragon-portable\bin\composer

после того как скачали, проверяем в консольке laragon версию

composer -V

должно выдать более менее актуальную дату

Ну теперь можно попробовать запустить команду установки

composer require "twig/twig:^3.0"

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

set http_proxy=172.27.100.5:4444
composer require "twig/twig:^3.0"

и ждем пока установится

и смотрим что у нас появилось в папке

давайте теперь попробуем воспользоваться twig.

Открываем index.php и пишем

<?php

// подключаем пакеты которые установили через composer
require_once '../vendor/autoload.php';

// создаем загрузчик шаблонов, и указываем папку с шаблонами
// \Twig\Loader\FilesystemLoader -- это типа как в C# писать Twig.Loader.FilesystemLoader, 
// только слеш вместо точек
$loader = new \Twig\Loader\FilesystemLoader('../views');

// создаем собственно экземпляр Twig с помощью которого будет рендерить
$twig = new \Twig\Environment($loader);

$url = $_SERVER["REQUEST_URI"];

// ..

кстати если у вас подсвечивается красным имена классов поставьте плагин PHP Intelephense

и после установки, закройте и откройте студию чтобы он подцепил классы Twig.

Рендерим с помощью twig

идея в общем такая, мы вместо того чтобы подключить файлы с помощью require будем вызывать команды twig а он будет уже собственно ренедрить за нас файлы, делается так:

<?php

// ...

$url = $_SERVER["REQUEST_URI"];

if ($url == "/") {
    // это убираем require "../views/main.html";
    
    echo $twig->render("main.html");
} elseif (preg_match("#/mermaid#", $url)) {
    // и это тоже require "../views/mermaid.html";
    
    echo $twig->render("mermaid.html");
} elseif (preg_match("#/uranus#", $url)) {
    // и вот это require "../views/uranus.html";
    
    echo $twig->render("uranus.html");
}

пока особых преимуществ не видно, ну только что ../views/ не надо писать

можно потыкать как работает, по идее должно работать как обычно:

сразу ставим себе плагин для работы с twig

Теперь, сделаем базовый шаблон разметки, назовем его __layout.twig

загоняем туда стандартную разметку html (через ! + Tab)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- но с небольшой правкой, это будет место под индивидуальный код страниц -->
    {% block content %}
        пустота
    {% endblock %}
</body>
</html>

теперь давайте наследуем этот шаблон нашими страницами. Сначала идем в main.html и правим там:

{% extends "__layout.twig" %}

<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>

проверяем:

получаем ошибку что у нас в main.html разметка находится во вне какого-нибудь блока который определен в шаблоне. Давайте запихаем ее вот так:

{% extends "__layout.twig" %}

{% block content %}
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
{% endblock %}

ну рендерится теперь

но все равно пока не впечатляет… о чем сыр-бор то?

Терпенье мой друг! =О

Давайте теперь тоже самое сделаем с остальными файлами, с mermaid.html

{% extends "__layout.twig" %}

{% block content %}
<img src="/images/mermaid.jpg" style="width: 300px;"/>
{% endblock %}

и с uranus.html

{% extends "__layout.twig" %}

{% block content %}
<img src="/images/uranus.png" style="width: 300px;"/>
{% endblock %}

а теперь сделаем магию, возьмем и в нашем базовом шаблоне добавим навигацию:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div><!-- добавил -->
        <a href="/mermaid">Русалка</a>
        <a href="/uranus">Уран</a>
    </div>
    {% block content %}
        пустота
    {% endblock %}
</body>
</html>

смотрим:

Ооо! Меню теперь везде есть, на главной странице даже дважды, давайте там чего-нибудь напишем:

{% extends "__layout.twig" %}

{% block content %}
Я главная страница
{% endblock %}

а в шаблоне добавим ссылку на главную страницу

<div>
    <a href="/">Главная</a> <!-- добавил -->
    <a href="/mermaid">Русалка</a>
    <a href="/uranus">Уран</a>
</div>
{% block content %}
    пустота
{% endblock %}

О как:

Передаем данные в шаблон

Прежде чем что-то делать, давайте переименуем наши шаблоны в twig формат, чтобы нормально работал плагин для Twig

и в index.php поменяем:

$url = $_SERVER["REQUEST_URI"];

if ($url == "/") {
    echo $twig->render("main.twig");
} elseif (preg_match("#/mermaid#", $url)) {
    echo $twig->render("mermaid.twig");
} elseif (preg_match("#/uranus#", $url)) {
    echo $twig->render("uranus.twig");
}

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

давайте будем передавать название страницы в нашем роутере (я роутером называю наши ifы которые решают какой шаблон рендерить)

if ($url == "/") {
    echo $twig->render("main.twig", [
        "title" => "Главная" // в квадратных скобках создаем словарик с ключем title, значением "Главная"
    ]);
} elseif (preg_match("#/mermaid#", $url)) {
    echo $twig->render("mermaid.twig", [
        "title" => "Русалка"
    ]);
} elseif (preg_match("#/uranus#", $url)) {
    echo $twig->render("uranus.twig", [
        "title" => "Уран"
    ]);
}

а теперь подключим эту значение в шаблоне, идем в __layout.twig и вместо Document вставляем:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title> <!-- заменил Document на {{ title }}-->
</head>
<body>
   <!-- ... -->

то есть в двойных фигурных скобках мы указываем ключ значение, которого мы хотели бы вывести.

Смотрим на название вкладки

красота! =)

вообще у нас сейчас много копипасты в index.html, давайте немного отрефакторим наш код:

// ...
$url = $_SERVER["REQUEST_URI"];

// добавил две переменные
$title = "";
$template = "";

// тут теперь просто заполняю значение переменных
if ($url == "/") {
    $title = "Главная";
    $template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
    $title = "Русалка";
    $template = "mermaid.twig";
} elseif (preg_match("#/uranus#", $url)) {
    $title = "Уран";
    $template = "uranus.twig";
}

// рендеринг делаем один раз по заполненным переменным
echo $twig->render($template, [
    "title" => $title
]);

Обобщаем страницы с картинками

Давайте теперь поглядим на страницы с картинками, они в принципе ничем не отличаются, кроме как адресом картинки. А раз так, давайте сделаем один шаблон под картинки:

вопрос только что в адресе писать?

Вообще, можно завести переменную под картинку, вот так:

$title = "";
$template = "";
$image = ""; // добавил переменную

if ($url == "/") {
    $title = "Главная";
    $template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
    $title = "Русалка";
    $template = "base_image.twig"; // используем шаблон base_image теперь
    $image = "/images/mermaid.jpg"; // заполняю
} elseif (preg_match("#/uranus#", $url)) {
    $title = "Уран";
    $template = "base_image.twig"; // и тут тоже base_image
    $image = "/images/uranus.png"; // и здесь заполняю
}

echo $twig->render($template, [
    "title" => $title,
    "image" => $image, // передаю
]);

теперь можно в шаблоне base_image.twig подцепить:

{% extends "__layout.twig" %}

{% block content %}
<img src="{{ image }}" style="width: 300px;"/>
{% endblock %}

тестируем:

можно удалить отдельные шаблоны под русалку и уран:

вроде все ок, единственное у нас получается, что image передается и на главную страницу хотя там не нужен. Это не очень грамотно с точки зрения архитектуры.

Поэтому сделаем немного по-другому. Мы создадим пустой словарик который будет заполнятся нужными ключ-значениями по необходимости, вот так:

// ...
$url = $_SERVER["REQUEST_URI"];

$title = "";
$template = "";
// $image = ""; убираем

$context = []; // наш словарик, данные для шаблона принято называть контекстом

if ($url == "/") {
    $title = "Главная";
    $template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
    $title = "Русалка";
    $template = "base_image.twig";
    
    $context['image'] = "/images/mermaid.jpg"; // передаем в контекст ключ image
} elseif (preg_match("#/uranus#", $url)) {
    $title = "Уран";
    $template = "base_image.twig";
    
    $context['image'] = "/images/uranus.png"; // и тут передаем в контекст ключ image
}

// название не пихаю в контекст в роутере,
// потому что это отдельная сущность, общая для всех
$context['title'] = $title;

// ну и рендерю
echo $twig->render($template, $context);

Ну не прекрасно ли! =)

5

Подключаем шаблонизатор Twig для работы с разметкой более грамотно