• Блог
  • ZoomX. Файловые плагины

Итак, друзья, вышла версия 3.4.0, которую я анонсировал ещё месяц назад, но отложил из-за работы над pdoTools3. Я продолжаю продвигать фреймворковский подход при разработке на MODX. Пока времени хватает только на кодинг и документацию. Но я очень надеюсь, что дойдут руки и до миникурса по разработке сайтов на ZoomX. В планах снять серию видео.

Что же в этой версии новенького?

  • Файловые плагины.
  • Модификатор markdown.
  • Механизм кэширования сниппетов.
  • Короткие имена контроллеров в роутах.
  • Упрощённый вариант переадресации в роутах.
  • Событие «OnBeforeRouteProcess».
  • Доработана функция jsonx.
  • Функционал контейнера.

Файловые плагины

Возможно не все знают, но я уже решал эту задачу в своём дополнении Middlewares. В ZoomX я предлагаю доработанную версию файловых плагинов. Давайте познакомимся с ними.

Файловые плагины в отличие от файловых сниппетов представляют собой классы. Вот пример такого плагина.

<?php
//  core/elements/plugins/MyPlugin.php

class MyPlugin extends \Zoomx\Elements\Plugin
{
    // События и приоритеты.
    public static $events = [
        'OnMODXInit' => -101,
        'OnHandleRequest' => 0,
    ];
    public $disabled = false;

    
    public function OnMODXInit($scriptProperties = [])
    {
        // ...
    }
    
    public function OnHandleRequest()
    {
        // ...
    }
}

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

Для того, чтобы плагин сработал, его нужно подключить. Делается это специальном файле настроек elements.php, который должен находится в директории конфигов сайта /core/config/. В нём необходимо загрузить класс плагина и зарегистрировать его.

<?php
//  core/config/elements.php

# 1. Загрузка класса
include MODX_CORE_PATH . 'elements/plugins/MyPlugin.php';

# 2. Регистрация в системе
$elementService->registerPlugins([MyPlugin::class]);

Это самый простой вариант. Но я предлагаю более красивый и современный способ — автозагрузка классов через композер. В этом случае для классов нужно придумать пространство имён. Пусть это будет «Site\Plugins».

<?php

namespace Site\Plugins;

class MyPlugin extends \Zoomx\Elements\Plugin
{
...
}

Теперь это пространство имён нужно добавить в автозагрузчик.

<?php
//  core/config/elements.php

# 1. Регистрация пространства имён в автозагрузчике композера
zoomx()->getLoader()->addPsr4('Site\\Plugins\\', MODX_CORE_PATH . 'elements/plugins/');

# 2. Регистрация в системе
$elementService->registerPlugins([Site\Plugins\MyPlugin::class]);

Итак, как подключить свои файловые плагины, разобрались. А как подключить файловые плагины дополнений? Также просто. Нужно в дополнении создать такой же файл elements.php и положить его в папку /core/components/extra/. ZoomX автоматически его подключит и зарегистрирует указанные в нём плагины.

Дополнительно!

Файл elements.php может быть использован не только для подключения плагинов. В нём можно инициализировать дополнительный функционал файловых элементов. Если, например, у вас сниппеты (или чанки) расположены в разных папках и вам не хочется прописывать лишние пути в названии сниппета/чанка, то можно зарегистрировать эти пути. Для примера возьмём такой вызов:

{'@FILE /folder/folder/snippet.php'|snippet:[
    'tpl' => '@FILE tpl/rows/row.tpl'
]}  

Выглядит не очень. Исправляем.

<?php
//  core/config/elements.php

# 1. Добавляем ещё один путь к файловым сниппетам
$elementService->addSnippetPath('path/to/snippets/' . 'folder/folder/');  // Разделение для наглядности

# 2. Добавляем ещё один путь к чанкам и шаблонам Smarty
parserx()->addTemplateDir('path/to/chunks/tpl/rows/'); 

Теперь вы можете указать просто имена файлов.

{'@FILE snippet.php'|snippet:[
    'tpl' => '@FILE row.tpl'
]}

Код выглядит уже значительно приятнее.

Модификатор markdown

Уверен, можно не объяснять что это и для чего нужно.

...
{'content'|resource|markdown}
...

Механизм кэширования сниппетов

Сейчас MODX предлагает следующую логику кэширования — или сниппет никогда не кэшируется или кэшируется с сохранением результата на странице. Т.е. или он будет вызыватся при каждом запросе, или после первого выполнения он будет заменён на результат и больше работать не будет. Но этих вариантов бывает недостаточно. Приходилось писать сниппеты-обёртки, которые сохраняли результат на определённое время. Теперь это можно сделать прямо из коробки. В теге сниппета в атрибуте cache_lifetime нужно указать количество секунд для хранения результата в кэше.

{'name'|snippet:[...] cache_lifetime=3600 nocache}

Внимание!

Если включено кэширование шаблонов, то обязательно нужно указать атрибут nocache в теге. На странице должен остаться вызов сниппета, а не его результат.

Короткие имена контроллеров в роутах

Если все контроллеры находятся в одном пространстве имён, то зачем постоянно их указывать. Теперь в системной настройке zoomx_controller_namespace можно указать неймспейс контроллеров, а в роутах писать только имя класса.

$router->get('uri', ['MyController', 'method']); // Преобразуется в Zoomx\Controllers\MyController

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

$router->get('uri', ['\Another\Controllers\MyController', 'method']);

Упрощённый вариант переадресации в роутах

Если для переадресации не нужны никакие проверки, то можно воспользоваться специальным методом redirect маршрутизатора.

$router->redirect('uri_from', 'uri_to');
// С указанием кода
$router->redirect('uri_from', 'uri_to', 301);

Переадресовывать можно и роуты с масками. В URI переадресации нужно указать соответствующую переменную со знаком $ в фигурных скобках.

$router->redirect('articles/{id}', 'posts/{$id}');

Событие «OnBeforeRouteProcess»

Данное событие сработает перед вызовом хандлера роута (контроллера или фукнции). В нем можно выполнить определённые проверки и изменить стандартное поведение. В плагине будут доступны 2 переменных:

  • $uri — текущий URI.
  • $router — объект маршрутизатора. С помощью его метода getRouteVars() можно получить переменные роута (маршрута), указанные в масках. Метод setRouteVars() сохраняет переменные роута для дальнейшего использования.

Как пример — можно по id ресурса/пользователя получить объект и сохранить его обратно в роутер. Таким образом в методе контроллера будет уже не id, а объект ресурса/пользователя.

<?php

Namespace Site\Plugins;

class Plugin extends \Zoomx\Elements\Plugin
{
    public static $events = [
        'OnBeforeRouteProcess' => 0,
    ];

    public function OnBeforeRouteProcess($properties)
    {
        // Распаковываем массив в переменные $uri и $router.
        extract($properties); 
        // Получаем переменные маршрута
        $vars = $router->getRouteVars();
        // Получаем объект пользователя по полученному id
        $vars['user'] = $this->modx->getObject('modUser', (int)$vars['user']);
        // Сохраняем новые данные
        $router->setRouteVars($vars);
    }
}

В роуте вы вместо id получите объект пользователя.

$router->get('users/{user:\d+}', function($user) {
    // $user - объект пользователя
    return viewx('user.tpl');

});

Функция jsonx

Для удобства в эту функцию добавлен третий параметр для указания HTTP кода ответа.

$router->get('uri', function() {
    // получение данных
    ...
    
    return jsonx($array, $headers, 201); // Код ответа: 201 Created
});

Если код будет меньше 400, то переменная success будет равна true.

{
  success: true,
  data: {
      foo: "bar"
  },
  meta: {
  	total_time: "0.0230 s",
  	query_time: "0.0000 s",
  	php_time: "0.0230 s",
  	queries: 1,
  	memory: "2 048 kb"
  }
}

Для кода 400 и выше признак успешности будет false, а данные по ошибке будут в свойстве errors:

{
  success: false,
  data: {},
  errors: {
      foo: "bar" // данные из функции jsonx
  },
  meta: {
  	...
  }
}

Функционал контейнера

Ещё в главный сервис была добавлена возможность сохранять данные для последующего использования. Это часть функционала DI контейнера. Это промежуточная ступень к внедрению полноценного DI контейнера если появится потребность.

Сохранить в контейнер -

zoomx()->set('foo', $object);
// или так
zoomx(['foo' => $object]);

Затем в любом месте приложения можно получить сохранённую переменную.

$object = zoomx('foo');

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

Заключение

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


П.С. Я пока отключил в магазине эту версию по причине ошибки при установке. При новой установке всё Ок. Но при обновлении из-за кэша плагина предыдущей версии возникает ошибка. При проверке на этом сайте я перед установкой видимо что-то сохранил и кэш очистился. Поэтому новая версия встала без ошибок. А если кэш не удалить, то 500-я ошибка. После выходных пересоберу пакет с исправлением данного бага и выложу в магазин.

П.П.С. 27.12.2021 Версия доступна для скачивания.

0   949

Комментарии ()

  1. Макс 24 февраля 2022 # 0
    Здравствуйте. Спасибо за компонент, очень полезная вещь для непрофессионалов во фреймворках. Как раз необходим был роутинг и работа через IDE, так что начал тестировать ваш компонент с версии 3.4.1. Обнаружил то ли ошибку, то ли баг, то ли так и не должно работать.

    Если мы определяем переменную для pdoTools в файловом шаблоне smarty, в таком варианте все работает:
    {$params = [
    	'parents' => 0,
    	'limit' => 3,
    	'tpl' => '@INLINE {$pagetitle}'
    ]}
    {'pdoResources'|snippet:$params}
    

    Но если добавить флаг nocache, вылетает 500 ошибка «Argument 2 passed to Zoomx\Support\ElementService::runSnippet() must be of the type array, null given, called in ...\core\components\zoomx\smarty\plugins\modifier.snippet.php on line 25»:
    {'pdoResources'|snippet:$params nocache}
    или
    {nocache}
    {'pdoResources'|snippet:$params}
    {/nocache}
    

    В общем сломал голову уже, не понимаю, что ему не нравится.

    P.S. Вызывается в шаблоне именно так для примера, иногда требуется получить динамические параметры в переменную через сниппет, на pdoTools есть проекты с такими решениями, проблем не было.
    1. Сергей Шлоков 24 февраля 2022 # +1
      Спасибо за обратную связь! Скорее всего это баг. Проверю и исправлю.
      1. Сергей Шлоков 25 февраля 2022 # 0
        Не смог повторить ошибку. Возможно есть какая-то специфика. Пробовал на файловом шаблоне с включенным и отключенным кэшем. Ошибок нет.

        1. Макс 25 февраля 2022 # 0
          На всякий случай поставил на modhost чистый MODX, установил zoomX и pdoTools. Включил friendly_urls. Создал пару документов и набор параметров.

          // routes.php
          <?php
          /** @var FastRoute\RouteCollector  $router */
          /** @var modX  $modx */
          
          $router->get('/', function() use ($modx) {
          	return viewx('index.tpl');
          });
          
          // index.tpl
          {extends "base.tpl"}
          
          {block "content"}
          	<h1>{'longtitle'|resource:'pagetitle'}</h1>
          	<section>
          		{* Первая загрузка после сохранения документа ок, потом 500 ошибка *}
          		{$params = [
          			'parents' => 2,
          			'depth' => 0,
          			'limit' => 3,
          			'tpl' => '@INLINE {$pagetitle}'
          		]}
          		{*'pdoResources'|snippet:$params nocache*}
          		{*nocache}
          			{'pdoResources'|snippet:$params}
          		{/nocache*}
          		{*------------------------------------------------------------------*}
          
          		{* Проблемы с передачей переменных в include, описание внутри*}
          		{$parent = 2}
          		{include 'include.tpl' parent=$parent}
          		{*------------------------------------------------------------------*}
          
          		{* Переменная есть после сохранения документа, после обновления страницы пропадает *}
          		{$user = ['auth' => 'auth', 'guest' => 'guest']}
          		<strong>Авторизация</strong>
          		{auth nocache}
          			Авторизованный {$user['auth']}
          		{/auth}
          		{guest nocache}
          			Неавторизованный {$user['guest']}
          		{/guest}
          		{*------------------------------------------------------------------*}
          
          		
          		{* pdoResources@propertySet *}
          		<strong>pdoResources@articles</strong>
          		{'pdoResources@articles'|snippet nocache}
          
          		{* второй такой же блок не работает, параметры @articles потерялись, видимо я как раз второй на локалке тестировал *}
          		<strong>pdoResources@articles 2</strong>
          		{'pdoResources@articles'|snippet:[
          			'showLog' => 1
          		] nocache}
          		{*------------------------------------------------------------------*}
          
          		
          		{* этот блок самый стабильный *}
          		<strong>runSnippet</strong>
          		{$modx->runSnippet('pdoResources@articles') nocache}
          
          		<strong>runSnippet 2</strong>
          		{$modx->runSnippet('pdoResources@articles') nocache}
          
          		{* добавим время кэширования в такой конструкции → 500 ошибка, такого примера не было в документации, просто проверил по аналогии с '...|snippet:[...]:600 nocache'*}
          		<strong>runSnippet 3</strong>
          		{$modx->runSnippet('pdoResources@articles'):600 nocache}
          		{*------------------------------------------------------------------*}
          	</section>
          {/block}
          
          // include.tpl
          {* После сохранения документа выводит нормально, стоит нажать F5 параметр parent теряется*}
          <strong>pdoResources in include with $parent</strong>
          {'pdoResources'|snippet:[
          	'parents' => $parent,
          	'depth' => 0,
          	'limit' => 3,
          	'tpl' => '@INLINE {$pagetitle}'
          ] nocache}
          
          <strong>runSnippet in include with $parent</strong>
          {$modx->runSnippet('pdoResources',[
          	'parents' => $parent,
          	'depth' => 0,
          	'limit' => 3,
          	'tpl' => '@INLINE {$pagetitle}'
          ]) nocache}
          
          Иерархия документов:
          -Home(1)
          -Статьи(2)
          --Статья1(3)
          --Статья2(4)
          --Статья3(5)

          Набор параметров @articles:
          parents→2
          limit→3
          tpl→@INLINE {$pagetitle}
          1. Сергей Шлоков 25 февраля 2022 # +1
            В режиме кэширования шаблонов Smarty для переменных нужно также указывать nocache.

            А вот это совершенно удивительная конструкция
            {$modx->runSnippet('pdoResources@articles'):600 nocache}
            Время кэширования указывается в третьем параметре метода
            {$modx->runSnippet('pdoResources@articles', [], 600) nocache}
            С пропажей набора параметров при втором вызове разберусь. Моя недоработка.

            1. Макс 01 марта 2022 # 0
              С кэшем разобрался, спасибо, сейчас все работает.

              Конструкция это конечно дурацкая, извините, что отвлек, только разбираюсь.

              По плейсхолдеру ph так и не понял, не нашел в документации zoomX→шаблонизатор smarty ни $ph['tv...'] ни $_ph['tv...'], иначе бы не писал

              {${'tv.image_preview'}|phpthumbon:...} - работает
              {$ph или $_ph['tv. или просто image_preview']|phpthumbon:...} - не работает
              
              P.S.: с меня причитается, как закончу проект:v (на modx.pro есть ссылка для благодарности)
              1. Сергей Шлоков 01 марта 2022 # 0
                По плейсхолдеру ph так и не понял, не нашел в документации zoomX
                Оговорился. Я имел ввиду модификатор ph.
      2. Макс 24 февраля 2022 # 0
        Нашел еще пару проблем в шаблонах smarty:
        Вот такая конструкция не воспринимает наборы параметров, выборка идет без их учета:
        {'pdoResources@articleItem'|snippet:[
        	'itemClass' => 'h367',
        	'limit' => 1,
        	'tpl' => '@INLINE {$pagetitle}',
        	'showLog' => 1
        ]}
        Работает через $modx->runSnippet:
        {$modx->runSnippet('pdoResources@articleItem',[
        	'itemClass' => 'h367',
        	'limit' => 1,
        	'tpl' => ('@FILE templates/'|cat:('zoomx_theme'|config)|cat:'/chunks/articleItemBgImage.tpl')
        ]) nocache}
        
        Судя по всему шаблонизатор вообще не воспринимает переменные в параметрах:
        Выводит список полей, как будто tpl => ''
        {$params = ['tpl' => '@INLINE {$pagetitle}']}
        {$modx->runSnippet('pdoResources@articleItem',[
        	'itemClass' => 'h367',
        	'limit' => 1,
        	'tpl' => $params['tpl']
        ]) nocache}
        
        Ну и не совсем ошибка, скорее наблюдение, если поставить запятую после последнего параметра, краш с 500 ошибкой, хотя ожидаешь по правилам PHP, что в конце массива допускается запятая.

        Вы должны авторизоваться, чтобы оставлять комментарии.

        Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.