Пишем RESTful API - практика. Часть 1.

Эта заметка продолжает цикл посвященный всестороннему обзору архитектуры REST для MODX.

Здесь можно почитать введение
Здесь быстрый осмотр встроенного в MODX модуля modRestService

Ну а начиная с этой заметки — поговорим уже о нюансах настоящей боевой разработки.


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

Возьмем хотя бы авторизацию. Согласно официальной документации выполняя первый шаг — первоначальное подключение API мы видим вот такую картину

<?php
// Загрузить MODX
require_once dirname(dirname(__FILE__)) . '/config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('web');
$modx->getService('error','error.modError', '', '');
// Загрузить любые классы или пакеты (модели), которые вам потребуются
$path = $modx->getOption('mypackage.core_path', null,
  $modx->getOption('core_path').'components/mypackage/') . 'model/mypackage/';
$modx->getService('mypackage', 'myPackage', $path);
// Загрузить класс modRestService и передать ему некоторую базовую конфигурацию
$rest = $modx->getService('rest', 'rest.modRestService', '', array(
   'basePath' => dirname(__FILE__) . '/Controllers/',
   'controllerClassSeparator' => '',
   'controllerClassPrefix' => 'MyController',
   'xmlRootNode' => 'response',
));
// Подготовить запрос
$rest->prepare();
// Удостовериться, что пользователю предоставлены необходимые права доступа; вернуть пользователю ошибку 401 в обратном случае
if (!$rest->checkPermissions()) {
   $rest->sendUnauthorized(true);
}
// Выполнить запрос
$rest->process();


Обратите внимание на вот такие строки

if (!$rest->checkPermissions()) {
   $rest->sendUnauthorized(true);
}

Казалось бы — ну да все правильно. Авторизован, есть права — продолжай, не авторизован — иди лесом.
Но стоит пройти вглубь метода — мы увидим следующую картину

public function checkPermissions() {
        return true;
}

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

Итак план на сегодня у нас такой:
1. Заводим свой кастомный компонент в каталоге /core/components/
2. В нем создаем класс-наследник modRestService, который можно переписывать как угодно
3. Настраиваем точку входа в rest (или базовую обвязку, кто как говорит), таким образом, чтобы REST сервис слушал наш компонент, а не стандартный модуль MODX

Погнали

Что касается создания компонента — в данном цикле я не ставлю целью выпустить тиражируемый, автоматически устанавливаемый модуль. Мы просто создадим набор классов, который MODX будет считать классом. Хотя вы пишите конечно, если надо — сделаем болванку с базовым набором контроллеров для того же miniShop2. Может и пригодится кому.

Шаг 1 — заводим компонент



Для создания компонента — по канону заводим в каталоге /core/components/ новый каталог, назовем его предсказуемо rest. Внутри rest — создаем каталог model, где в свою очередь добавим первый файл — restservice.class.php. Постфикс .class в наименовании файла обязателен опять же по канону. Метод $modx->getService() при помощи которого обычно вызывают классы компонентов ищет php файлы *.class.php по указанному пути.

Пока получилось у нас вот что


Идем дальше…
Файл класса мы создали, нужно написать в нем базовое содержание. Оно будет очень простым
1. Подключаем класс MODX modrestservice.class.php который мы хотим наследовать и переопределить
2. Создаем наш класс, его имя должно быть таким же как наименование файла. Имя файла и класса кстати может быть любым, не обязательно restService. Но имя файла и класса должны соответствовать друг другу.
3. Указываем что наш класс будет наследовать подключенный выше modRestService

<?php

require_once MODX_CORE_PATH . 'model/modx/rest/modrestservice.class.php';

class restService extends modRestService
{
    
}
Теперь у нас, по идее доступны любые методы из класса modRestService, которые мы можем как хотим переопределять. В том числе и метод проверки прав, о котором я упоминал выше.

public function checkPermissions() {
        return true;
}
С него мы и начнем работу. Добавим его в класс. Пока оставим как есть, без изменений

<?php

require_once MODX_CORE_PATH . 'model/modx/rest/modrestservice.class.php';

class restService extends modRestService
{
    /**
     * Check permissions for the request.
     *
     * @return boolean
     */
    public function checkPermissions()
    {
    // Здесь позже напишем логику проверки авторизации и прав    
        return true;
    }
}

Шаг 2 — Вызываем компонент в точке входа



Как вы должны помнить ранее, в быстром старте, в корне проекта мы создавали каталог /rest/ и файл index.php — который называется точкой входа. В нем мы вызывали стандартный модуль modRestService
Перепишем точку входа таким образом, чтобы мы могли вызывать собственный компонент, унаследовавший все возможности modRestService
<?php
// Здесь типовые вещи, без изменений
// Подключаем MODX
require_once dirname(dirname(__FILE__)) . '/config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('web');
$modx->getService('error', 'error.modError', '', '');


// Изменения получаем лишь в этом блоке кода
// Подготовим  некоторую базовую конфигурацию и загрузим свой собственный класс restService
// Мы всего навсего изменили второй параметр, указали имя свежесозданного класса, и третий параметр - путь к классу

/** @var modRestService $rest */
$rest = $modx->getService('rest', 'restService', MODX_CORE_PATH . 'components/rest/model/', array(
    // Путь к контроллерм позже тоже поменяем, пока не трогам
    'basePath' => dirname(__FILE__) . '/Controllers/',
    'controllerClassSeparator' => '',
    // Префикс для контроллеров также для разнообразия позже поменяем. 
    'controllerClassPrefix' => 'MyController',
    'xmlRootNode' => 'response',
));


//Далее тоже только старые методы без изменений

// обрабатывем запрос и возвроащаем ответ
/** @var modRestService $rest */
$rest->prepare();
// Проверяем, что пользователю предоставлены необходимые права доступа;
// Бьем по рукам и Возвращаем пользователю ошибку 401 ежели чего
if (!$rest->checkPermissions()) {
    $rest->sendUnauthorized(true);
}

$rest->process();
На данный момент можем проверить работает ли вообще REST, не сломали ли ничего.
Вспоминаем как мы делали это в быстром старте — обращались по адресу /rest/resources/

У меня работает


Далее проверяем сможем ли мы переопределять методы modRestService. Самый простой способ — сделать вот так (запрещаем доступ)
<?php

require_once MODX_CORE_PATH . 'model/modx/rest/modrestservice.class.php';

class restService extends modRestService
{
    /**
     * Check permissions for the request.
     *
     * @return boolean
     */
    public function checkPermissions()
    {
        //Запрещаем доступ
        return false;
    }
}

Если все сделали правильно — получаем примерно вот такую картину



Для одной статьи достаточно. В продолжении поговорим об инструментарии тестирования (не в браузере же работать) и об авторизации
Николай Савин
05 октября 2019, 08:56
modx.pro
5
551
+18
Поблагодарить автора Отправить деньги

Комментарии: 8

Сергей Шлоков
05 октября 2019, 09:38
+1
Следуя парадигме современной разработки, использовать папку model нужно только для моделей. Т.е. класс модели xPDO, схема и мап-файл. Остальному там не место.
Сергей Шлоков
05 октября 2019, 09:57
+1
Мысли в воздух.
Не обязательно расширять сервис ради проверки прав. Мы можем сделать любую проверку самостоятельно
// Вместо этой проверки
if (!$rest->checkPermissions()) {
    $rest->sendUnauthorized(true);
}
// Пишем свою 
if (!$modx->hasPermission('some_permission')) {
    $modx->sendUnauthorizedPage();
    // echo json_encode(['success' => false, 'message' => 'Permission denied.']); exit;
}
    Николай Савин
    05 октября 2019, 10:01
    0
    Да конечно, по всякому можно. А я так хочу. Вдруг мне понадобится дальше и другие методы класса переопределить.
      Сергей Шлоков
      05 октября 2019, 10:08
      0
      Это не претензия. Просто пример одной из возможных реализаций. Можно и так и эдак и по всякому.
        Михаил
        05 октября 2019, 20:06
        0
        Камастура программирования ))))))
    Руслан Сафин
    06 октября 2019, 23:04
    +2
    Изучаю ларавель последнее время, апи на нем построить намного проще, не приходится велосипедный велосипед изобретать)))
    Плохого про модикс ничего сказать не хочу (до сих пор на нем мелкие сайты делаю)
      Николай Савин
      07 октября 2019, 05:21
      +1
      Да кто спорит. Laravel — это API-ориентированный продукт.
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      8