ZoomX - альтернативная шаблонизация

Версия 3.2.0-pl

Введение

Главная идея данного компонента - полный отказ от стандартного шаблонизатора в пользу PHP шаблонизаторов. Никаких элементов в БД, многократного парсинга контента и логики в стиле сниппета IF. Теперь вся разработка в удобном IDE редакторе, плюс полноценная поддержка версионности, а также мощный функционал и огромное сообщество разработчиков. Для управления шаблонами добавлена библиотека FastRoute, которая позволяет гибко управлять маршрутизацией запросов. Но в отличие от других пакетов MODX с подобным функционалом (CustomRequest, modxFastRouter, VirtualPage), которые срабатывают на событие OnPageNotFound после того как MODX не нашёл указанного ресурса, в ZoomX FastRoute запускается перед стандартным маршрутизатором MODX и имеет 3 режима работы - отключён, совместный и строгий.

Кроме того на основе FastRoute реазизована поддержка REST архитектуры, что позволяет строить полноценные API приложения.

Внимание!

Данный компонент требует PHP ≥ 7.1.

Использование

Установите компонент используя стандартный установщик пакетов. Включите системную настройку friendly_urls. Следующим шагом нужно настроить роуты и Smarty шаблоны. Из коробки идут 3 шаблона - "index.tpl", "base.tpl" и "error.tpl". Изначально шаблоны находятся в папке core/components/zoomx/templates/default/. Но её можно переопределить. Даже нужно. Ведь эти шаблоны будут перезаписываться при каждом обновлении компонента.

За работу с шаблонами отвечают 2 системные настройки - zoomx_template_dir (по-умолчанию, core/components/zoomx/templates/) и zoomx_theme (по-умолчанию, default). Я советую перенести шаблоны в папку core/templates/default/.

Темы используются для удобного разделения представлений. Например, шаблоны для десктопной версии сайта можно разместить в теме "default", а мобильную версию, соответственно, в папке "mobile". Если темы совсем не нужны, то настройку zoomx_theme можно оставить пустой.

Теперь можно создавать свои собственные шаблоны. Вы можете сделать это для всех ресурсов или только для части. Используйте шаблоны, идущие с компонентом, для образца. Далее эти шаблоны нужно связать с соответствующими ресурсами. Делается это через роуты.

Маршрутизатор

Маршруты (роуты) настраиваются в файле core/config/routes.php. Чтобы подключить шаблон, нужно вернуть объект класса ZoomView. Альтернативный вариант - использовать хелпер viewx().
# 1. Класс
$router->get('hello.html', function() {
    // Нужно вернуть объект класса ZoomView. Первый параметр - название шаблона, второй - данные для шаблона.
    return new ZoomView('hello.tpl', ['name' => 'John']);
});
# 2. Хелпер
$router->get('users/{id}', function($id) use($modx) {
    $user = $modx->getObject('modUser', ['id' => (int)$id]);
    // Можно использовать хелпер.
    return viewx('profile.tpl', $user->toArray());
});

Подробнее про роуты можно узнать в разделе "Маршрутизация".

Важно!

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

Можно вернуть просто строку:

$router->get('hello.html', function() {
    return '<h1>Hello, John!</h1>';
});

Информация!

Предыдущий пример может вернуть ошибку 404. Избежать этого можно если:

  • Существует ресурс с указанным URI.
  • В системной настройке zoomx_autoload_resource отключена автозагрузка ресурсов для всех роутов.

Автозагрузку ресурса можно отключить и в отдельном роуте. Таким образом мы получим виртуальную страницу.

$router->get('hello.html', function() {
    // Отключить автозагрузку ресурса = виртуальная страница
    zoomx()->autoloadResource(false);
    
    return '<h1>Hello, John!</h1>';
});

Пример редиректа. Можно использовать как стандартный метод modX::sendRedirect(), так и переопределить переменную $modx->resourceIdentifier, указав в ней или id нужного ресурса или URI. В последнем случае для переопределённого ресурса будет использован указанный шаблон.

$router->get('product1.html', function() use($modx) {
    $modx->sendRedirect('catalog/product2.html');
});
// Использование идентификатора ресурса
$router->get('resource.html', function() use($modx) {
    // можно указать id
    $modx->resourceIdentifier = 2;  
    // или URI ресурса
    $modx->resourceIdentifier = 'another.html';
    
    return viewx('hello.tpl', ['name' => 'John']);
});

Важно понимать!

Так как маршрутизатор срабатывают в самом начале запроса, то в роутах ресурс ещё не определён ($modx->resource = null). Менеджер запроса подгрузит его позже. Но если вам очень нужен ресурс именно в роуте, то придётся самостоятельно его запросить. В этом случае автоматический поиск задействован не будет.

$router->get('article100.html', function() use($modx) {
    zoomx()->getResource('article100'); // Без расширения html
    return viewx('resource.tpl');
});

Режимы маршрутизации

Маршрутизатор может работать в 3-х режимах:

  • 0 – отключен. Все указанные роуты игнорируются.
  • 1 – смешанный (мягкий). Если для указанного URI роут не найден, то MODX продолжит обработку запроса в обычном режиме. Т.е. вы можете работать в режиме PHP шаблонизатора с отдельными ресурсами.
  • 2 – строгий (монопольный). Если для указанного URI роут не найден, то обработка запроса будет завершена с ошибкой 404. Шаблонизатор MODX не запускается.

Режимы указываются в системной настройке zoomx_routing_mode.

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

$router->get('blog/{uri:.+}', ['ArticleController', 'show']);

Для запроса site.ru/blog/abra-cadabra-fake указанный выше роут подойдёт, но ресурса с таким URI найдено не будет. Если бы роут не был найден, то ZoomX передал бы дальнейшую обработку запроса MODX и последний сам бы занимался поиском ресурса. Но так как роут сработал, то ZoomX будет работать как в строгом режиме. Т.е. выведет свою страницу ошибки. Таким образом, ключевым моментом является наличие роута для URI запроса.

Контроллеры

Всю логику обработки роутов лучше переносить в контроллеры. Это стандартная практика в мире фреймворков. Например, ресурс site.ru/users используется для работы с пользователями:

// Список пользователей
$router->get('users', ['Zoomx\Controllers\UserController', 'index']);
// Данные конкретного пользователя
$router->get('users/{id:\d+}', ['Zoomx\Controllers\UserController', 'show']);
// Добавить пользователя
$router->post('users', [Zoomx\Controllers\UserController::class, 'create']);

Метод index можно опустить - достаточно указать только класс контроллера

$router->get('users', Zoomx\Controllers\UserController::class);

К сожалению формы умеют работать только с методами GET и POST. Для того, чтобы использовать другие HTTP методы, необходимо включить системную настройку "zoomx_http_method_override" (по-умолчанию, включена) и добавить в форму скрытый инпут с именем "_method" и названием метода.

<form>
    <input type="hidden" name="_method" value="PUT">
    ...
</form>

После этого будет работать следующий роут

// Обновление пользователя
$router->put('users/{id:\d+}', ['Zoomx\Controllers\UserController', 'update']); // в методе update нужно указать параметр $id

Внимание!

Для некоторых роутов придётся самостоятельно определять ресурс $modx->resource.

В большинстве случаев в обычном режиме с методами PUT, PATCH и DELETE работают редко. В основном они используются в API режиме.

Контроллеры располагаются в папке core/components/zoomx/src/Controllers/. Пользовательский контроллер можно разместить в ней. Этот контроллер должен расширять базовый контроллер ZoomX\Controllers\Controller.

<?php

namespace Zoomx\Controllers;

class UserController extends Controller
{
    public function index()
    {
        return viewx('users.tpl');
    }

    public function show($id)
    {
        // Самосоятельно находим ресурс, предназначенный для вывода данных пользователя.
        zoomx()->getResource('users');
        // Находим пользователя.
        $user = $this->modx->getObject('modUser', ['id' => $id]);
        // Передаёт объект $user в шаблон user.tpl
        return viewx('user.tpl', compact('user'));
    }
}

RESTful API

Кроме управления шаблонами FastRoute предлагает полноценный механизм RESTful API. В MODX уже есть встроенный функционал поддержки этого архитектурного стиля. Но он крайне ограничен и отстаёт от современных стандартов. Он требует создание отдельной точки входа и может работать только с одной сущностью.

Принцип построения REST запросов

Метод HTTP URI Метод контроллера
GET /users index
GET /users/create create
POST /users store
GET /users/{id} show
GET /users/{id}/edit edit
PUT/PATCH /users/{id} update
DELETE /users/{id} delete

Данный режим включается автоматически при наличии заголовка Accept со значением application/json. В этом режиме нет автоматического определения ресурса по URI. Запрос обрабатывается в контроллере или замыкании роутера и результат сразу возвращается обратно клиенту.

Шаблонизатор Smarty

В ZoomX не используется встроенный шаблонизатор MODX. Его возможности очень ограничены. В современной разработке используют отдельные PHP шаблонизаторы. И один такой уже используется в MODX. Это Smarty. У него мощные функциональные возможности. Он один из самых быстрых. И его не нужно ставить дополнительно - он уже есть в ядре. Тем, кто пользуется Fenom, легко освоит и Smarty, так как разработчик Fenom взял за основу именно Smarty и его синтаксис.

Для работы с этим шаблонизатором необходимо определить место для хранения его шаблонов. В ZoomX из коробки идёт 3 шаблона - "base.tpl", "error.tpl" и "index.tpl". Найти их можно в директории core/components/zoomx/templates/default/. Но использовать их не желательно. Они больше для примера и быстрого старта. Лучше создать новую папку. Например, core/templates/. И в неё скопировать папку default вместе с её содержимым. Таким образом, ваши изменения в базовых шаблонах не пропадут при обновлении компонента.

После этого необходимо в системных настройках указать новый путь к файлам шаблонов. В настройке zoomx_template_dir нужно указать первую часть - "core/components/zoomx/templates/". В настройке zoomx_theme - последнюю папку "default". Если вы не хотите использовать темы, то последнюю настройку оставьте пустой. Но в этом случае файлы шаблонов нужно перенести в папку core/components/zoomx/templates/.

Структура папок может быть такая

templates
|--default
|  |-- partials
|  |   |-- head.tpl
|  |   |-- header.tpl
|  |   |-- navigation.tpl
|  |   |-- footer.tpl
|  |-- chunks
|  |   |-- chunk.tpl
|  |   |-- chunk2.tpl
|  |-- base.tpl
|  |-- error.tpl
|  |-- index.tpl

В корневой папке находятся главные файлы шаблонов. В папке partials содержатся подшаблоны страницы. В папке chunks - чанки для использования в сниппетах. Тогда шаблон может выглядеть так

<!doctype html>
<html lang="{'cultureKey'|config}">
{include "partials/head.tpl"}
<body>
<div class="container">
<!-- Хэдер страницы, содержащий блок навигации (главное меню) -->
{include "partials/header.tpl"}
{block "content"}{/block}
</div>
<!-- Футер страницы -->
{include "partials/footer.tpl"}
{block "scripts"}{/block}
</body>
</html>

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

<!doctype html>
<html lang="{'cultureKey'|config}">
<head>
    {block "title"}<title>{'pagetitle'|resource} - {'site_name'|config}</title>{/block}
    <base href="{'site_url'|config}" />
    <meta charset="{'modx_charset'|config}" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    {block "styles"}{/block}
</head>
...

В расширяющем шаблоне можно изменить блоки title и styles.

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