Из этого урока Вы узнаете как можно быстро создать RESTful API для любого сайта на любой CMS, MODX — это только пример.
Для создания API я буду использовать:
Результат здесь:
https://github.com/andchir/modx2-api
Всё описанное я проделывал на Linux, но я думаю, что на Windows разница в командах будет не большая. Предварительно я установил Composer и утилиту Symfony.
Создание проекта и классов пользователей
Создаю проект:
composer create-project symfony/skeleton modx2-api
Устанавливаю необходимые пакеты для создания API:
cd modx2-api
composer req api
composer req migrations
composer req form
composer req maker --dev
Запускаю локальный сервер.
Если установлена утилита Symfony:
symfony server:start
или так:
php -S 127.0.0.1:8000 -t public
Открываю в браузере адрес для проверки, что всё работает:
http://127.0.0.1:8000/
Открываю файл .env и редактирую строку подключения к базе данных проекта на MODX:
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
Теперь нужно создать пользователя и настроить систему авторизации по логину и паролю.
Устанавливаю дополнительные пакеты:
composer req jwt-auth
composer req orm-fixtures --dev
composer req profiler --dev
В файле .env появились новые параметры пакета "jwt-authentication-bundle".
Создаю классы сущности и репозитория (Doctrine ORM) пользователя:
php bin/console make:user
Появились два файла:
src/Entity/User.php
src/Repository/UserRepository.php
Создаю таблицу "user" в базе данных:
bin/console doctrine:schema:create
Настраиваю авторизацию пользователей в соответствии с инструкциями:
- https://symfony.com/doc/current/security/form_login_setup.html
- https://api-platform.com/docs/core/jwt/#configuring-the-symfony-securitybundle
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\Type\LoginType;
use App\Form\Type\UpdateProfileType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="app_login")
* @param AuthenticationUtils $authenticationUtils
* @return Response
*/
public function loginAction(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('api_entrypoint');
}
$form = $this->createForm(LoginType::class);
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'form' => $form->createView(),
'last_username' => $lastUsername,
'error' => $error
]);
}
}
Генерирую класс для создания пользователей:
php bin/console make:fixtures
Подробнее здесь: https://symfony.com/doc/current/security.html#a-create-your-user-class
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserFixtures extends Fixture
{
private $passwordEncoder;
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public function load(ObjectManager $manager)
{
$user = new User();
$user
->setEmail('admin@admin.com')
->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
$user->setPassword($this->passwordEncoder->encodePassword(
$user,
'admin'// пароль
));
$manager->persist($user);
$manager->flush();
}
}
Создаю администратора с адресом почты "admin@admin.com" и паролем "admin":
php bin/console doctrine:fixtures:load --append --group=UserFixtures
Позже эти данные можно будет изменить.
Генерирую ключи в папке config/jwt/:
jwt_passhrase=$(grep ''^JWT_PASSPHRASE='' .env | cut -f 2 -d ''='')
echo "$jwt_passhrase" | openssl genpkey -out config/jwt/private.pem -pass stdin -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096
echo "$jwt_passhrase" | openssl pkey -in config/jwt/private.pem -passin stdin -out config/jwt/public.pem -pubout
setfacl -R -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
setfacl -dR -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
Проверяю создание токена:
curl -X POST -H "Content-Type: application/json" http://localhost:8000/authentication_token -d '{"email":"admin@admin.com","password":"admin"}'
Создаю миграцию:
php bin/console make:migration
Теперь самое интересное.
Генерация API и документации
Генерирую классы сущностей для всех таблиц базы данных:
php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
Генерирую геттеры и сеттеры для классов:
php bin/console make:entity --regenerate App
Открываю код одного класса, например src/Entity/ModxSiteContent.php. Добавляю аннотацию @ApiResource:
API для ModxSiteContent готово.
Открываю страницу http://localhost:8000/api
Беру токен, нажимаю кнопку "Authorize", вставляю строку с токеном:
Bearer MY_TOKEN
Нажимаю кнопку "Try it out" и затем кнопку "Execute". Получаю такой результат:
Связи таблиц
Я не буду описывать как создаются фильтры, чтобы не дублировать документацию, но приведу пример как можно создать связи для таблиц, т.к. это немного сложнее.
В случае MODX данные пользователей хранятся в отдельной таблице "user_attributes". Например, мне нужно в выборку пользователей по GET запросу добавить их email, имя, телефон и т.д. Открываю код класса App\Entity\ModxUsers, добавляю приватное свойство $attributes
и описываю связь с классом App\Entity\ModxUserAttributes в аннотоции "@ORM\OneToOne":
/**
* @var ModxUserAttributes
*
* @ORM\OneToOne(targetEntity="ModxUserAttributes", mappedBy="user")
* @Groups({"read", "attributes"})
*/
private $attributes;
Снова добавляю недостающие геттеры и сеттеры:
php bin/console make:entity --regenerate App\\Entity\\ModxUsers
Обратите внимание, что я добавил группу "attributes" в аннотацию @ApiResource
Открываю код класса App\Entity\ModxUserAttributes, добавляю аннотацию @ApiResource
и связь с классом App\Entity\ModxUsers
:
/**
* @var ModxUsers
*
* @ORM\OneToOne(targetEntity="ModxUsers", mappedBy="attributes")
* @ORM\JoinColumn(name="internalKey", referencedColumnName="id")
*/
private $user;
Всем свойствам, которые нужно добавить в выборку, вставляю аннотацию @Groups({"attributes"})
.
Результат:
В итоге для авторизации в вашем приложении вам нужно сначала отправить логин (email) и пароль на URL "/authentication_token", получить токен и потом этот токен отправлять в каждом запросе в заголовке "Authorization":
Если проект https://github.com/andchir/modx2-api будет интересен пользователям, он будет развиваться. Также жду PR от всех желающих помочь.
Комментарии (11)
pavelgvozdb
09.12.2019 06:54Но ведь это только работа с БД. В MODX есть ещё, к примеру процессоры, которые тоже запускают некоторую логику и перед, и после добавления ресурса. А данное API эти правки опускает, работая напрямую с БД. При чём тогда тут MODX?
Andchir Автор
09.12.2019 11:35В MODX есть ещё, к примеру процессоры, которые тоже запускают некоторую логику и перед, и после добавления ресурса.
Согласен, есть, например, плагины, которые нужно запускать. Во-первых бывают ситуации когда вам нужно только просматривать информацию, но не добавлять и редактировать. В этом случае такое API потребует значительно меньше времени. Сделать возможность запуска плагинов, думаю, не сложно. Это можно в будущем реализовать. Опять же, это будет быстрее, чем делать с нуля на MODX.
Dmi3yy
09.12.2019 09:33+1Не пойму где это применить на практике?
Если делаем проект на MODX с рестфул то создаем свое апи под проект и нет смысла открывать доступ ко всему.
Если делаем как базу для рестфул админки то зачем тут Симфони?
Если уже используем Симфони то зачем нам МОДХ?
Andchir Автор
09.12.2019 11:25Не пойму где это применить на практике?
Например заказчику нужно мобильное приложение, где он мог бы добавлять страницы, просматривать пользователей или заказы в интернет-магазине. Так же можно использовать для создания облегченной версии админки под конкретный проект. Там некоторый функционал MODX можно не дублировать.
Если делаем как базу для рестфул админки то зачем тут Симфони?
Симфони затем, что Api Platform использует именно этот фреймворк.
Если уже используем Симфони то зачем нам МОДХ?
Вполне могу представить ситуацию, когда заказчику понравилась CMS MODX (или любая другая), вы сделали сайт, а потом он захотел мобильное приложение. Думаю использовать API Platform будет быстрее, чем писать API на MODX.
Dmi3yy
09.12.2019 11:43На выходе получим ситуацию:
У клиента был сайт на MODX и он мог всегда найти разработчика любого который знает MODX
А после такой интеграции ему уже нужен разработчик который знает и MODX и Symfony
Как по мне такой подход хуже чем написать api в рамках MODX с учетом что задача эта решается пускай и дольше чем через api платформ но не сложнее.
Вообщем я бы очень осторожно смешивал разные системы в одном проекте ибо потом очень сложно это все поддерживать, говорю на опыте поддержки:
MODX Revo + Laravel
MODX Evo + Kohana
Притом в обеих случаях качество кода было не очень по причине что разработчик кто это делал не мог простые вещи реализовать в рамках MODX и из за этого и прикрутил фреймворк
Andchir Автор
09.12.2019 11:56Да, в данном решении есть свои минусы, но есть и плюсы. Решение нужно выбирать под конкретный проект. Я бы использовал данное решение только для приложений, для которых нужно только чтение (read-only). Таких задач тоже бывает не мало.
А после такой интеграции ему уже нужен разработчик который знает и MODX и Symfony
Зависит от сложности приложения, которое будет использовать это API. Если оно простое, то знание Симфони не обязательно, достаточно добавить аннотацию @ApiResource классу и API для таблицы БД готово.Andchir Автор
09.12.2019 12:27MODX часто используют не программисты, а верстальщики. Они используют стандартный функционал и дополнения. Данное АПИ тоже можно считать дополнением. Если какого-то функционала не хватает, то не программист (или программист, который не работает с Симфони) может добавить свою хотелку (feature-request) github.com/andchir/modx2-api/issues
OnYourLips
09.12.2019 11:08Открываю файл .env и редактирую строку подключения к базе данных проекта на MODX
Не .env, а .env.local
Andchir Автор
09.12.2019 12:14Можно создать (скопировать) .env.local из .env и указать там параметры подключения к БД и т.п. А можно создать .env из .env.dist. Кому как удобнее. Но ваш вариант более стандартный и предпочтительный, согласен.
PerlPower
Красота! Тот редкий случай когда API обертка над сущностями CMS создается действительно быстро и без костылей.
Dmi3yy
это обертка над сущностями БД а не сущьностями CMS, да просто и красиво, но что б использовать полноценно как обертку над CMS нужно дописывать кучу логики.