Заготовка MODX RESTFull API для работы с магазином на основе miniShop2

Подготовил и выложил на Github полностью готовый компонент, который реализует RESTFull API на базе встроенного в MODX modRestService.

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


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

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

Возможности заготовки


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

Итак в компоненте присутствуют уже готовые контроллеры позволяющие
  • Получить список разделов и конкретный раздел
  • Получить список товаров и конкретный товар, как через id так и через alias по вкусу. В том числе с дополнительной обработкой свойств
  • Работа с корзиной. Добавление товара в корзину, удаление, изменение, очистка
  • Работа с заказом. Добавление данных, валидация, создание заказа.
  • Способы оплаты
  • Способы доставки
Таким образом сразу после установки уже доступна возможность через API найти нужный товар, добавить его в корзину, создать и отправить заказ с возможностью отработки всех внутренних плагинов MODX и miniShop2, которые позволят, к примеру отправить заказ дальше в CRM.

В корне проекта есть файл Readme где более полно описаны возможности компонента.



Установка и базовая настройка


1. Клонируйте из репозитария заготовку к себе в проект. Не забываем поставить звездочку.
2. Допиливаем что нужно или сразу же собираем компонент, запуская файл /_build/build.php
3. Далее немного конфигурируем сервер. Суть — сделать так, чтобы запросы отправленные на sitename.com/rest/connector_name подхватывались файлом /assets/components/shopapi/rest.api.php

у меня сделано вот так
location /rest/ {
    try_files $uri @modx_rest;
  }

  location @modx_rest {
    rewrite ^/rest/(.*)$ /assets/components/shopapi/rest.api.php?_rest=$1&$args last;
  }
Когда понадобилось сделать то же самое для сервера на основе apache, я не стал ковырять память в попытках вспомнить как же пробрасывать запросы в htaccess. Создал физический каталог rest в корне проекта и положил внутрь два файла
  1. htaccess со следующим содержимым
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-s
    RewriteRule ^(.*)$ index.php?_rest=$1 [QSA,NC,L]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(.*)$ index.php [QSA,NC,L]
  2. index.php скопированный из файла assets/components/shopapi/shop.api.php
4. Ну и на этом пожалуй все. Уже можно проверять. Про тестирование API запросов я писал чуть подробнее в одной из предыдущих статей.
Я тестирую все API запросы в программе Postman. Это очень удобно и бесплатно.



Для проверки пишем вот так:
получаем список продукции
GET  https://shopname.com/rest/products/
Отправляем товар в корзину
POST https://shopname.com/rest/cart
Обязательный параметр id товара product={product_id}
Обязательный параметр size (фасовка) size={size}
Не обязательный параметр count (количество). По умолчанию  count=1


Компонент изнутри. На что обратить внимание


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

Для начала главное. Если понять это — считайте все… Вы уже умеете делать REST API для MODX проекта. Суть всей работы заключается в контроллерах.
На каждый запрос (products, users, cart, order, news) создаем свой отдельный контроллер, который как сниппет сходит внутрь MODX-проекта, заберет данные, создаст или изменит какие то данные и вернет ответ. Вот и все. Все контроллеры подчиняются нескольким правилам, главное их соблюдать, а дальше насколько фантазии и опыта хватит.

Как и большинство других компонент работает из двух каталогов.

  • /assets/components/shopapi/
  • /core/components/shopapi/
Начнем с главного коннектора, который принимает и разруливает все запросы. Это файл
assets/components/shopapi/rest.api.php
Он довольно маленький — только базовая конфигурация. Менять ничего не нужно, все работает.
Нам нужно знать лишь про несколько строк. Обратите внимание на префикс shop — он нам пригодится, дальше. Префикс может быть любым.


Переходим к ядру компонента, расположенному в каталоге core/components/shopapi/

Главное на что здесь стоит обратить внимание — это каталог controllers/rest, в котором находятся все наши чудесные контроллеры. Именно с ним происходит 95% работы.

У меня контроллеров не особо много, я их сложил все на одном уровне.
При желании все контроллеры (если их много) можно сгруппировать и разложить внутри по разным каталогам. Вложенность может быть любой. Они все будут без проблем найдены, при помощи встроенного итератора каталогов.

Разберемся с правилами работы контроллеров. Их не много.

Именование файла контроллера
Когда вы обращаетесь через API по адресу shopname.com/rest/products/ — то цепочка запросов приводит нас к контроллеру products. Отсюда следует правило — имя контроллера должно совпадать с ячейкой URL.
Хотите получить список пользователей по адресу shopname.com/rest/users/ то будьте добры создать контроллер users.php в каталоге с контроллерами.

Именование класса контроллера
Далее помните префикс shop, который я просил запомнить выше? Сейчас мы с ним столкнемся.
Каждый контроллер это класс, имя которого состоит из префикса и имени контроллера. У меня префикс shop, контроллер products.php значит класс должен быть именован shopProducts. Если вы решите поработать с пользователями — создаете контроллер users.php, именуете его как shopUsers.

Наследование родительского класса modRestController
Каждый контроллер должен быть наследником главного REST класса MODX modRestController.
Но не обязательно напрямую. Хорошим тоном, является создание какого то базового, промежуточного наследника.
Это может быть какой то абстракный класс, который ничего не делает. Это может быть класс, который переопределяет или оптимизирует настройки modRestController
У меня таких базовых классов два. В зависимости от ситуации я наследую первый или второй.

Класс shopBaseRestController — он будет базовым контроллером, который будут наследовать все остальные контроллеры. В нем добавлена загрузка API miniShop2, заложена работа с pdoFetch, немного оптимизирован код MODX. Здесь же можно добавить загрузку и других необходимых для работы компонентов и классов.

Таким образом любой наш контроллер наследует shopBaseRestController, который в свою очередь наследует modRestController и получает все доступные возможности.

Как работает контроллер


Главное что нужно знать — MODX умеет и понимает четыре основных вида REST запросов.
  • GET — отвечает за показ общедоступной информации
  • POST — отвечает за создание нового, или просто за большие объемы информации
  • PUT — отвечает за редактирование
  • DELETE — удаление
Соотвественно у каждого контроллера есть 4 готовых метода get(), post(), put(), delete();
Они по сути и делают всю работу. То есть прикидываем какие виды запросов будет принимать контроллер, и реализовываем эти методы.

Некоторые контроллеры по умолчанию отвечают только на GET запросы. К примеру products — выдает список товаров. Остальные типы запросов ему не нужны, их нужно запретить. Для этого в каждом контроллере нужно заполнить свойство, которое разрешает указанные глаголы, и, как следствие, запрещает не указанные. По умолчанию разрешен только GET.
public $allowedMethods = array('get, post');

А вот в корзине например уже все типы запросов реализованы.
GET выдает текущий состав корзины
POST — добавляет товар
PUT — обновляет количество (или удаляет товар, если передан count = 0)
DELETE — очищает корзину
В корзине в свойстве разрешенные методы, нужно перечислить все.

Реализация методов
Здесь уже можно писать привычный для MODX код.
Хотите получаете данные через XPDO, хотите через PDO Fetch (кстати, а есть среди читающих те, кто не понимает разницы и не знает почему иногда XPDO это зло?)

По сути мы делаем какой то запрос в базу или к другим компонентам, совсем как в сниппетах и возвращаем ответ с ошибкой или успехом.
return $this->failure('Произошла ошибка', $data);
return $this->success('Вот ваши данные', $data);

Рассмотрим чуть подробнее как работает метод get() который отрабатывает при GET запросах.
Метод get() всего навсего смотрит если есть входящий параметр $id (он получится если в адресе написать после контроллера цифру /rest/products/55) — вызывается метод read($id). То есть из базы будет загружаться одна запись. Если id нет — вызывается метод getList(). То есть получаем из базы список записей.

По умолчанию эти методы тоже реализованы в MODX, но они меня не устраивают, я их переписал, используя pdoFetch. Посмотрите на контроллеры products, categories.

метод post() работает еще проще. Он принимает параметры через методы
$this->getProperties();
$this->getProperty('name');
Если надо как то фильтрует или валидирует данные и создает новый объект.

Вот пожалуй и все. Я постарался расписать все более менее базовые вещи в рамках одной статьи, но в любом случае это болванка — лишь образец, используя который можно готовить API для своих проектов. Пробуйте, изучайте код, пишите в комментариях, если что то не получается — попробуем разобраться.
Николай Савин
08 марта 2020, 15:07
modx.pro
13
2 456
+22
Поблагодарить автора Отправить деньги

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

Николай Савин
08 марта 2020, 17:33
+3
Изложение получилось сумбурное, торопливое — т.к. тема объемная на самом деле. Будем считать это черновиком. Постараюсь по настроение подкорректировать текст, снабдив наглядностью.
    Сергей Шлоков
    09 марта 2020, 07:14
    +1
    Постараюсь по настроение подкорректировать текст
    Всегда надо перечитывать свой текст. Это называется вычитка. Вылавливаешь ошибки
    Возможности заготовкки
    и конструкции типа
    Особенно обратите внимание на префикс shop. Обратите внимание на префикс shop — он нам пригодится, дальше. Префикс может быть любым.
    А как получить все продукты из определённого каталога?
      Николай Савин
      09 марта 2020, 07:17
      0
      Всегда надо перечитывать свой текст. Это называется вычитка. Вылавливаешь ошибки
      Дядь Сережа — я как раз этим занимаюсь.
      Фильтр по родителю вот такой
      GET https://shopname.com/rest/products?parent=13
        Сергей Шлоков
        09 марта 2020, 08:22
        +5
        Я ровно так и думал. ;) MODX отстаёт от сегодняшнего уровня разработки во всём. В том числе и в поддержке REST архитектуры. Так как я на некоторое время погружался в эту тему, то с определённой долей уверенности могу это утверждать. Я даже нацарапал статейку про «недоRESTfull API» в MODX.

        Общепринятым форматом для описания REST API на сегодняшний день является OpenAPI, который также известен как Swagger.
        Согласно ему твой запрос должен выглядеть так
        GET https://shopname.com/rest/catalogs/13/products
        А аргументы используются для дополнительных действий типа "?search=телевизоры". Хотя тут больше свободы. В итоге у тебя есть готовый эндпойнт, для которого автоматом пишется документация.

        Конечно, никто не заставляет под дулом пистолета следовать OpenAPI. То же самое можно сказать и про стандарты PSR. Но считается дурным тоном им не следовать. Поэтому я считаю, что делать что-то новое нужно с учетом текущих требований.

        Представь, как было бы удобно, если бы у всех платформ API следовали бы единому стандарту, а не так как сейчас
        https://api.vk.com/method/users.get?user_id=210700286&v=5.52
        https://api.ok.ru/api/friends/get
        https://graph.facebook.com/{page-id}/conversations?fields=id,messages{message,from}
        Это как единый стандарт разъема для телефонов.
          Николай Савин
          09 марта 2020, 08:30
          0
          Ну… приходится иметь дело с тем, что предложили авторы. Моя задача была показать основные принципы работы из коробки.
          Посмотрим — может получится реализовать вариант OpenAPI. Но вообще вряд ли.
          Проще подготовить собственную надстройку к MODX, по-моему.
            Сергей Шлоков
            09 марта 2020, 09:42
            0
            Тема нужная. Вот бы раньше об этом узнать. Тогда разработчики компонентов для MODX делали бы свои коннекторы по единому стандарту.
    Баха Волков
    08 марта 2020, 23:39
    +4
    Комментариев нет, странно
    Так что ради справедливости, выражаю благодарность от лица сообщества в котором состоят Баха, Bakha и ещё много других Бах.
      sensoria
      08 сентября 2020, 03:54
      +1
      Присоединяюсь к Бахе. И хотел бы тоже поблагодарить, добрыми словами Николая. «Спасибо», очень познавательно.
        Роман
        10 ноября 2020, 15:36
        0
        Николай подскажите, как вызвать все данные из Заказа (msOrder и msOrderAddress)?
          Николай Савин
          10 ноября 2020, 17:00
          0
          Так же как и в любом другом случае.
          $msOrder = $modx->getObject('msOrder', array('id' => $id));
          $msAddress = $msOrder->getOne('Address');
          Судя по тому что вы знаете название классов вы в курсе как это делается.
          sensoria
          16 ноября 2020, 16:40
          0
          Николай, как вы реализовали shop.krendel.kz/rest/products
          format_size: [
          {
              value: 1,
              size: "1 кг.",
              price: "4 000"
          },
          {
              value: 1.5,
              size: "1.5 кг.",
              price: "6 000"
          }
          ]
            Николай Савин
            16 ноября 2020, 20:16
            0
            Напишите мне в телеграм biz87 — скину контроллер посмотреть
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            12