В данном уроке мы рассмотрим разработку REST API на основе OctoberCMS.
Для чего это может понадобиться? Например, для создания роутов для последующего обращения к ним через AJAX.
Тут читатель может возразить, что в October для этого есть встроенный AJAX-фреймворк, работающий с компонентами и методами, находящимися прямо в файле страницы и будет прав. Но в некоторых случаях встроенный AJAX-фреймворк может быть не удобен или совсем не подходить. Особенно, при внедрении сторонних библиотек с динамической подгрузкой данных, таких как Select2.
Также, как показала практика, OctoberCMS отлично подходит как основа для headless решений, предоставляющих REST API для SPA или мобильных приложений, с удобной админкой в комплекте.
Приступая к работе
Как известно, OctoberCMS основан на Laravel. Из этого следует, что разработка REST API на OctoberCMS будет очень похожа на разработку REST API на Laravel.
Данный урок подразумевает, что Вы уже умеете создавать плагины для OctoberCMS. Также нам понадобится развернутый проект и немного свободного времени.
Начало
Для начала необходимо создать плагин. Для этого, как обычно, пойдем в консоль и попросим artisan создать нам пустой плагин.
$ php artisan create:plugin Sandbox.RestApi
Или создадим всё вручную (или даже через Builder), кому как больше нравится.
Далее, для объявления роутов нашего REST API создадим файл /plugins/sandbox/restapi/routes.php и объявим в нем наш первый роут.
// /plugins/sandbox/restapi/routes.php
<?php
use Illuminate\Support\Facades\Route;
Route::prefix('api')->group(function () {
Route::get('hello', function () {
return response('hello rest api');
});
});
Теперь, если обратиться к этому роуту, то мы увидим ответ - hello rest api. Для этого мы обычно используем Postman.
Далее - все как и в случае с Laravel. Только папку для контроллеров нам придется создать самостоятельно. Создадим наш контроллер, например, в файле /plugins/sandbox/restapi/http/controllers/HelloController.php
, и добавим роут для него:
// /plugins/sandbox/restapi/http/controllers/HelloController.php
<?php
namespace Sandbox\Restapi\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;
class HelloController extends Controller
{
public function greet(string $name): JsonResponse
{
return response()->json([
'greet' => "Hello $name",
]);
}
}
// /plugins/sandbox/restapi/routes.php
<?php
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use Sandbox\RestApi\Http\Controllers\HelloController;
Route::prefix('api')->group(function () {
Route::get('hello/{name}', [HelloController::class, 'greet']);
});
Теперь обратимся к роуту/api/hello/John%20Doe
и увидим, что наш API отвечает соответствующим образом.
Главное здесь - наследовать контроллеры от Illuminate\Routing\Controller
, то есть от базового контроллера Laravel. А не от Backend\Classes\Controller
, так как последний предназначен для создания страниц в админ-панели OctoberCMS и плохо подходит для нашей задачи.
Особенности
OctoberCMS - это, в первую очередь, система предназначенная для разработки сайтов с классическим роутингом и генерацией HTML-страниц на сервере, а не REST API, поэтому некоторые вещи для разработки REST API подготовлены не очень хорошо.
Основные вопросы - обработка исключений и CORS.
Обработка исключений
В Laravel, если мы запросим роут страницы через браузер и в процессе обработки запроса будет выброшено исключение - мы увидим красивую страницу с кучей полезной информации. В случае же с XHR-запросом - получим json с текстом ошибки, кодом и стеком вызовов. И происходит это автоматически, то есть стандартный обработчик ошибок в Laravel сам понимает, какой ответ нам нужен. В случае с OctoberCMS это не так.
Чтобы в этом убедиться, создадим роут, который, просто выбросит ошибку валидации, а затем обратимся к этому роуту.
// /plugins/sandbox/restapi/routes.php
<?php
use October\Rain\Exception\ValidationException;
Route::get('/exception', function () {
throw new ValidationException([
'something' => 'wrong',
'mode' => 'error',
]);
});
Откроем Postman и запросим наш роут, не забывая при этом добавить заголовок X-Requested-With
со значением XMLHttpRequest
, чтобы приложение понимало, что у нас тут AJAX-запрос и отправлять отрендеренную страницу не нужно.
Не густо! Что ж, значит нам придется сделать обработку исключений самим.
Согласно документации, для этого надо в методе boot
класса Plugin
нашего плагина добавить конструкцию вида:
// /plugins/sandbox/restapi/Plugin.php
<?php
namespace Sandbox\RestApi;
use Config;
use Fruitcake\Cors\HandleCors;
use Illuminate\Contracts\Http\Kernel;
class Plugin {
// ...
public function boot(): void
{
App::error(function (ValidationException $exception) {
return response()->json([
'message' => 'validation exception',
'errors' => $exception->getErrors()
], 422);
});
}
// ...
}
Теперь попробуем обратиться к роуту еще раз.
Уже лучше. Но теперь появилась другая проблема: так как мы переопределили обработку для ошибок валидации, то у нас теперь не будет работать стандартная валидация в админке и на фронте.
Для проверки этого утверждения, создадим пустой контроллер для админки, добавим в него метод index
и бросим ошибку валидации.
// /plugins/sandbox/restapi/controllers/Hello.php
<?php
namespace Sandbox\Restapi\Controllers;
use Backend\Classes\Controller;
use October\Rain\Exception\ValidationException;
class Hello extends Controller
{
public function index()
{
throw new ValidationException([
'something' => 'wrong',
'mode' => 'error',
]);
}
}
Теперь откроем эту страницу в браузере.
Да - это не то, что мы привыкли видеть в браузере, в случае ошибки.
Для решения этой проблемы мы перенесем регистрацию хэндлера ошибок валидации в новый middleware
.
Создадим класс ExceptionsMiddleware
, разместим его в /plugins/sandbox/restapi/http/middleware/ExceptionsMiddleware.php
и добавим следующий код:
// /plugins/sandbox/restapi/http/middleware/ExceptionsMiddleware.php
<?php
namespace Sandbox\Restapi\Http\Middleware;
use App;
use Closure;
use Illuminate\Http\Request;
use October\Rain\Exception\ValidationException;
class ExceptionsMiddleware
{
public function handle(Request $request, Closure $next)
{
$this->registerExceptionHandlers();
return $next($request);
}
private function registerExceptionHandlers(): void
{
App::error(function (ValidationException $exception) {
return response()->json([
'message' => 'validation exception',
'errors' => $exception->getErrors()
], 422);
});
}
}
Добавим наш новый middleware
в файл routes.php
// /plugins/sandbox/restapi/routes.php
<?php
use Sandbox\Restapi\Http\Middleware\ExceptionsMiddleware;
Route::group([
'prefix' => 'api',
'middleware' => [
ExceptionsMiddleware::class
]
], function () {
// наши роуты
});
Если же мы откроем страницу теперь, то увидим, привычную нам картину - роут с ошибкой будет отдавать все, что нам нужно.
CORS
Следующий интересный вопрос - CORS.
Если Вы не знаете, что такое CORS, то CORS (Cross-Origin Resource Sharing) - это механизм, позволяющий с помощью дополнительных заголовков запросов и ответов получать доступ к ресурсам других доменов. Подробнее можно почитать здесь.
В Laravel, начиная с 7-ой версии, есть встроенный middleware
для работы с CORS. В OctoberCMS же его нет. А это значит, что этот вопрос нам придется решить самим.
В Laravel встроенный middleware
работает на основе пакета fruitcake/laravel-cors
. Не будем изобретать велосипед и поступим так же.
Про особенности работы с composer в OctoberCMS рекомендую ознакомиться с документацией. В нашем случае, будем считать, что разрабатываемый плагин предназначен исключительно для текущего проекта, а значит все зависимости будем добавлять в корневой composer.json.
Идем в консоль и просим composer установить нам этот пакет.
composer req fruitcake/laravel-cors
Данный пакет требует конфига. Следуя рекомендациям из документации OctoberCMS создем конфиг не в общей папке проекта, а в папке плагина /plugins/sandbox/restapi/config/cors.php
// /plugins/sandbox/restapi/config/cors.php
<?php
return [
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
Для чего нужен каждый параметр можно посмотреть здесь.
Затем регистрируем конфиг в методе boot
класса Plugin
нашего плагина, а также зарегистрируем ServiceProvider пакета и добавим глобальный middleware.
<?php
namespace Sandbox\RestApi;
use Config;
use Fruitcake\Cors\HandleCors;
use Illuminate\Contracts\Http\Kernel;
class Plugin {
// ...
public function boot(): void
{
Config::set('cors', Config::get('sandbox.restapi::cors'));
$this->app->register(CorsServiceProvider::class);
$this->app[Kernel::class]->pushMiddleware(HandleCors::class);
}
// ...
}
Заключение
Вещи описанные в статье, на самом деле, не обязательные, и разрабатывать REST API на OctoberCMS можно и без них, однако обработка исключений не плохо упрощает жизнь, а CORS - необходимая вещь, если вы разрабатываете API обращение к которому будет происходить с домена отличного от того, на котором это API находится.
Ссылки
Оригинал статьи
Исходный код примера