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

ПРИМЕЧАНИЕ. Код примера не является полным, не выполняет никаких проверок безопасности. Это означает доказательство концепции и блоков для понимания работы с плагинами.

Начальная настройка

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

Сохраним это в своем собственном модуле, который легко использовать повторно. Мы будем держать все в порядке и идти с Myth\Template в качестве основного пространства имён. Сохраним это в основном каталоге прямо рядом с приложением и системными папками:

/application
/system
/Myth
    /Template

Осталось только сделать, чтобы CodeIgniter знал, где искать модуль, поэтому откройте application/Config/Autoload.php и добавьте новое пространство имен в массив $psr4 в конструкторе класса:

$psr4 = [
    'Config' => APPPATH.'Config',
    'App'  => APPPATH,
    'Myth\Template' => ROOTPATH.'Myth/Template'
];

Регистрация плагинов

Каждый плагин Parser должен быть зарегистрирован в Config\View.php, чтобы Parser мог найти плагины. При написании большего дополнения, подобного этому, которое в конечном итоге объединит несколько различных плагинов, становится довольно громоздким, чтобы каждый раз вводить каждый плагин для этого вручную. Например, каждая функция в системе шаблонов, такая как команды расширения (extend), блока (block) и вставки (insert), должна быть реализована как собственный плагин. Однако есть более простой способ.

Недавно добавленная функция позволяет любому классу конфигурации (который простирается от BaseConfig) для поддержки классов регистратора. Эти классы просто добавляют любое количество записей в файл конфигурации. Один класс регистратора может использоваться для любого количества файлов конфигурации, что делает их идеальными для доставки с помощью bundles/modules/packages.

Прежде чем мы перейдем к подробностям о том, как они работают, нам необходимо убедиться, что файл конфигурации View знает об этом. Добавьте в application/Config/View.php
 

protected $registrars = [
    MythTemplateRegistrar::class
];

Все перечисленные здесь классы будут зацикливаться, вызывая в нем статический метод, который соответствует имени файла Config, без пространства имен и расширения. В этом случае наш файл конфигурации - View.php, поэтому должен быть статический метод, называемый View(). Создайте класс в Myth/Template/Registrar.php и добавьте следующий код:
 

<?php namespace MythTemplate;

class Registrar
{
    public static function View()
    {
        return [
            'plugins' => [
                'extend' => [ function($str, array $params=[]) { return Engine::instance()->extend($str, $params); } ],
                'block'  => [ function($str, array $params=[]) { return Engine::instance()->block($str, $params); } ],
            ]
        ];
    }
}

Это регистрирует два новых плагина, extend и block. Оба этих плагина ожидают работать с содержимым между тегами открытия и закрытия, поэтому сами функции обернуты внутри массива. Значения каждого из них могут быть любым допустимым допустимым типом. В этом случае мы используем блокировки, которые захватывают экземпляр основного класса шаблонов шаблонов и запускают соответствующий метод. Здесь нет ничего сложного.

Движок

Сам движок шаблона - это один файл класса, называемый движком. Вот скелетный класс:
 

<?php namespace MythTemplate;

use Config\Services;

class Engine
{
    protected static $instance;
    protected $parser;
    protected $blocks = [];

    public function __construct()
    {
        $this->parser = Services::parser();
    }

    public static function instance()
    {
        if (is_null(self::$instance))
        {
            self::$instance = new Engine();
        }

        return self::$instance;
    }

    public function extend(string $body, array $params = []) { }

    public function block(string $body, array $params=[]) { }

}

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

Сначала это статическая переменная класса $instance. Она просто поддерживает singleton класса. Поскольку блоки и шаблон визуализируются в разное время, нам нужно одно место для хранения бит и частей финальной страницы, пока мы не готовы собрать все это вместе. Использование singleton дает нам то, что нам нужно. Экземпляр извлекается через метод instance(), как показано в определении плагина.

В конструкторе мы собираем копию анализатора CodeIgniter и храним его, поэтому нам не нужно создавать его экземпляр. Хотя это сделано явно здесь, лучшим решением было бы передать его через конструктор, но это осталось для вас реализовать.

У нас есть массив $blocks, который хранит все отображенные блоки, которые необходимо вставить в файл макета.

И, наконец, у нас есть два метода, которые мы вызываем из определений плагина. Сейчас мы посмотрим на них.

{+ extend +}

Цель расширения (extend) - определить, какой основной макет/шаблон должен включать в себя содержимое этого представления. Таким образом, он должен принимать имя файла, поскольку это только параметр, который мы будем называть tpl. Из-за того, как плагины работают, если вы хотите использовать контент, мы должны обернуть содержимое в парах открывающих и закрывающих тегов, поэтому в наших представлениях мы сделаем что-то подобное:

{+ extend tpl=master +}
    ... custom content here ...
{+ /extend +}

ПРИМЕЧАНИЕ. Пока еще не реализовано, класс Parser будет обновлен в будущем, чтобы удалить необходимость указывать параметры, но мы покажем, что работает в настоящее время.
 

public function extend(string $body, array $params = [])
{
    if (! array_key_exists('tpl', $params))
    {
        throw new BadMethodCallException('Must provide tpl value to extend a template.');
    }

    // Parse the contents since it will likely be
    // filling blocks inside the template itself.
    $this->parser->renderString($body, null, true);

    // Parse our template last, so that all child
    // blocks will have had time to populate this->blocks.
    $out = $this->parser->render($params['tpl'], null, true);

    return $out;
}

Когда этот плагин вызывается, $body содержит все HTML-коды между тегами открытия и закрытия. Сначала проверим, существует ли параметр tpl или исключение. Затем мы запускаем тело через сам анализатор. Мы должны сделать это, чтобы на странице запускались любые плагины, для нас наиболее важными являются любые вызовы {+ block +}, которые будут хранить данные блока локально в классе Engine.

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

{+ block +}

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

Простым файлом шаблона может быть:

<html>
    <body>
        {+ block name=content +}
            <h1>Default Content\</h1>
        {+ /block +}
    </body>
</html>

И отдельный файл вида может выглядеть так:
 

{+ extend tpl=master +}

    {+ block name=content +}
        <h1>New Content\</h1>
    (+ /block +}

(+ /extend +}

Итак, давайте посмотрим, как это можно реализовать:
 

public function block(string $body, array $params=[])
{
    if (! array_key_exists('name', $params))
    {
        throw new BadMethodCallException('Template blocks must have a name');
    }

    $name = $params['name'];

    // If we already have content for that (from a child view)
    // then simply return it.
    if (array_key_exists($name, $this->blocks)) return $this->blocks[$name];

    // Otherwise, save the content locally since we're inserting that
    // into the parent template somewhere.
    $this->blocks[$name] = $body;

    return $body;
}

Во-первых, мы гарантируем, что параметр name существует. Теперь, если у нас уже есть контент (что означает, что мы находимся в файле шаблона), возвращаем существующий контент. В противном случае сохраните тело, которое мы получили (часть между ярлыками открытия и закрытия блока) и вернем его. Таким образом, независимо от того, находимся ли мы в файле представления или в шаблоне, мы покажем правильный контент: либо переопределенный контент, либо по умолчанию.

Ссылки по теме

Источник: Creating A Simple Template Engine in CI4