• Блог
  • [ZoomX] Контроллеры, RESTful API

Друзья, представляю на ваш суд новую мажорную версию компонента ZoomX. Она значительно доработана. В ней появился режим RESTful API, добавлена поддержка контроллеров для полноценной работы в режиме роутинга, добавлены исключения (exceptions) для более удобного управления поведением запроса и много чего другого. Вот список всех доработок:

  • Появилась возможность полноценной работы в формате RESTful API.
  • Добавлена поддержка контроллеров в роутерах.
  • Добавлено событие OnRequestError для ошибок, коды которых отличаются от 401, 403 и 404.
  • Добавлены короткие модификаторы «js», «css» and «html», которые являются псевдонимами модификаторов «jstobottom», «csstohead» and «htmltobottom» соответственно.
  • Добавлена поддержка MODX синтаксиса тегов ({'*pagetitle'}, {'%lexicon'}, {'++setting'}, {'~5'}).
  • Переработаны модификаторы «url» и «lexicon».
  • Переименована системная настройка «zoomx_routs_mode» в «zoomx_routing_mode».

Рассмотрим все изменения по порядку.

RESTful API

Теперь в ZoomX есть полноценная поддержка архитектуры REST. Причем запросы не нужно отправлять на отдельную точку входа, как это сейчас делают многие. Достаточно отправить обычный запрос на сервер. Главное в этом запросе не забыть указать заголовок Accept со значением application/json. Можно, конечно, и не указывать, результат будет такой же. Но тогда будет работать стандартный обработчик запроса и выполнять лишние для API запроса действия. Так что лучше сразу указать MODX, что это API запрос.

Для информации!

Режим API отличается от обычного режима тем, что в нём нет поиска документа по URI. Т.е. пришёл запрос, вы его обработали в контроллере или замыкании и вернули результат. А в обычном режиме идёт поиск документа по URI, и если он не найден, то возвращается ошибка. Причём, хочу обратить ваше внимание, поиск документа (ресурса) идёт автоматически, а не как во фреймворках, где в контроллере роута нужно самостоятельно делать запрос в базу данных. Эта логика используется в ZoomX только для всех остальных объектов системы.

$router->get('users/{id}', function(int $id) use($modx) {
  if (!$user = $modx->getObject('modUser', ['id' => $id])) {
      abortx('404', 'Указанный пользователь не найден.');
  }
  return viewx('profile.tpl', $user->toArray());
});

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

$router->get('someUri', function() use($modx) {
  $modx->request->getResource('', 'anotheUri'); // Можно указать как URI, так и ID документа.
  return viewx('view.tpl');
});

API режим подразумевает ответ в JSON формате. Поэтому обработчик роута (замыкание или контроллер) должен вернуть массив, который автоматически закодируется в JSON.

$router->get('api/foo', function() {
    return ['foo' => 'bar']);
});

// Ответ
{
  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"
  }
}

Формат ответа соответствует спецификации JSON:API.

Если нужно указать дополнительные заголовки, то используйте хелпер jsonx(). Первым параметром передаётся массив с данными, вторым — массив заголовков.

$router->get('api', function() {
    return jsonx(['foo' => 'bar'], ['CustomHeader' => 'Value']);
});

Чтобы вернуть ошибку, нужно вызвать специальный хелпер abortx() с кодом ошибки.

$router->get('profile', function() use($modx) {
    if (!$modx->user->isAuthenticated()) {
        abortx(401, 'Вы должны залогиниться!');
    }
    return jsonx($modx->user->Profile->toArray());
});

Этот хелпер прерывает работу скрипта и выводит ответ с соответствующим HTTP заголовком. В данном примере 401 Unauthorized. Для этого используются исключения. Из коробки идёт 8 исключений — для ошибок 400, 401, 403, 404, 405, 406, 415 и 503. Их можно расширять для адаптации к задаче и добавлять свои.

Внимание!

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

Контроллеры

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

$router->get('users', ['Zoomx\Controllers\UserController', 'index']);
$router->post('users', ['Zoomx\Controllers\UserController', 'create']);

// Метод index можно опустить - достаточно указать только класс контроллера
$router->get('users', Zoomx\Controllers\UserController::class);

Событие OnRequestError

Как вы знаете, в MODX есть 2 события для ошибок — OnPageNotFound и OnPageUnauthorized. А в ZoomX добавлен ряд ошибок и для них эти события не подходят. Поэтому я добавил ещё одно общее событие OnRequestError, которое будет срабатывать как на ошибки, указанные выше (400, 405, 406, 415 и 503), так и на добавленные пользователем.

Вывод ошибок

Для вывода ошибок из коробки идёт шаблон error.tpl. Он используется для всех указанных выше ошибок. Если вам нужно персонифицировать какую-то ошибку, то создайте шаблон с именем этой ошибки. Например, для ошибки 404 файл шаблона должен называться 404.tpl. Тогда он автоматически подтянется для вызова abortx('404').

Короткие модификаторы «js», «css» and «html»

Они всего лишь являются псевдонимами модификаторов «jstobottom», «csstohead» and «htmltobottom» соответственно. По правилам разработки яваскрипт добавляется в конец страницы, стили в HEAD, а разметка HTML в BODY. Поэтому зачем писать такие длинные названия? )

MODX синтаксис тегов

Данная фича предлагает пользователям, привыкшим к стандартному синтаксису MODX, пользоваться похожим синтаксисом для более комфортной работы. На данный момент поддерживаются следующие теги — {'*pagetitle'}, {'%lexicon'}, {'++setting'}, {'~5'}. Но я бы советовал по возможности не использовать этот синтаксис, так как это создаёт дополнительную, хоть и небольшую, нагрузку на парсер.

Модификаторы «url» и «lexicon»

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

// Было
# 1. Сначала нужно загрузить лексикон
{$modx->lexicon->load('ru:custom:default')}
# 2. Теперь можно вывести сообщение
{'some_message'|lexicon:['id' => 5]:'ru'} 

// Стало
{'some_message'|lexicon:['id' => 5]:'ru:custom:default'} 

В модификаторе url изменения коснулись параметров. Изначально, как и в Fenom, в модификаторе есть несколько параметров, которые зеркалят метод modX::makeUrl(). В итоге, если вам нужно указать схему (третий параметр), то придётся указать и все предыдущие. Теперь достаточно сразу в первом параметре в массиве передать нужный аргумент.

// Было
{5|url:'':[]:-1}

// Стало
{5|url:['scheme' => -1]} 

Отмечу, что работает и старый формат. Т.е. можно пользоваться любым удобным вариантом.

Заключение

Вот такая получилась версия. Все изменения не раскроешь в короткой статье. Да это и не нужно. Постараюсь сделать это более подробно в доступной форме. Очень хотелось бы, чтобы и в RU сообществе обратили внимание на этот компонент и мы все вместе довели его до совершенства и сделали MODX более современным. Я уже установил его на этот сайт и впечатления самые положительные — вся разработка перенеслась в PhpStorm со всеми вытекающими плюшками. Но хотелось бы услышать больше отзывов и предложений.

Следующий важный шаг — замена pdoTools на свою библиотеку. Тем и займусь.

1   352

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

  1. Семён 24 февраля 2021, 11:08 # 0
    Сергей приветствую!
    Столкнулся с проблемой при использовании сниппета pdoPage в шаблонах smarty
    Есть такой вызов прямо в шаблоне смарти
    {'pdoPage'|snippet:[
          'element'=>'pdoResources',
          'parents'=>5,
          'pageLimit'=>4,
          'tpl'=>'@INLINE {$pagetitle}',
          'limit'=>10
          ] nocache}
    
    
          <div class="flex justify-center mb-6">
            {'page.nav'|ph nocache}
          </div>
    
    Всё выводится нормально и результат и пагинация, но в ссылках пагинации некорректно сформированы адреса для страниц, а именно отсутствует адрес самой страницы. В ссылке есть лишь гет-параметры.
    Во всех чанках этого сниппета за адрес ссылки отвечает плейсхолдер [[+href]]
    Так вот когда я использую fenom в шаблонах — ссылки формируются корректно, но когда smarty — нет
    В чем тут может быть загвоздка?
    1. Сергей Шлоков 25 февраля 2021, 11:52 # 0
      Дело в том, что сниппеты pdoTools работают только с шаблонизаторами MODX и Fenom. Это зашито намертво в ядре. Поэтому Смарти не парсится.

      Я сейчас и занимаюсь вопросом замены pdoTools для ZoomX. Практически всё уже есть в modHelpers. Осталось добавить некоторые моменты типа работы с деревом ресурсов и написать примеры.
      1. Семён 25 февраля 2021, 11:59 # 0
        У меня всё с выводом нормально, и сниппет отрабатывает и плейсхолдер выводится, проблема именно в формировании одно плейсхолдера уже внутри хлебных крошек, то есть смарти туда по идее даже не лезет, там всё другие парсеры делают. Просто странно, что с включенным fenom в шаблонах плейсхолдер корректно формируется, а при смарти только его часть.
        При чем я побовал обернуть всё это в блоки с парсерами MODX и Pdo и всё равно такой результат.
        Хотя в этом же случае смарти вообще не лезет в эти блоки
        1. Сергей Шлоков 25 февраля 2021, 15:19 # 0
          Блин. Мой косяк. Короче, в MODX для ЧПУ запросов используют старый престарый код
          // .htaccess
          # The Friendly URLs part
          RewriteCond %{REQUEST_FILENAME} !-f
          RewriteCond %{REQUEST_FILENAME} !-d
          RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
          
          Т.е. URI записывается в GET параметр с именем «q». Ну 10 лет назад ещё ладно. Но сейчас в этом нет необходимости. Достаточно просто перенаправить на index.php
          ...
          RewriteRule ^ index.php [L]
          
          Для работы с адресной строкой есть много Request менеджеров. Да банально просто в $_SERVER['REQUEST_URI'] глянуть.

          Так вот, Василий работает именно с этим GET параметром q. И оттуда берёт URI страницы. А я в ZoomX работаю как сейчас принято и поэтому удаляю этот параметр, чтоб не мешал разработчикам. Чего делать, не знаю. Наверно, нужно его оставлять на такие случаи. Ё моё. Как много завязано на легаси.
          1. Сергей Шлоков 25 февраля 2021, 19:52 # 0
            Завтра выпущу обновление с исправлением данной проблемы.
            1. Семён 25 февраля 2021, 21:09 # 0
              Благодарю за разъяснение, буду ждать обновление.

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

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