Расширение API с помощью дополнений

Основная цель

Дополнения призваны расширить/изменить функциональность API без внесения изменений в основной код. Вы легко можете создать свой собственное дополнение. В рамках дополнения доступно:

  • Добавление новой точки входа API (endpoint). Точкой входа является URL вида /shops/items/1, входе выполнения запроса на данный URL в итоге будет вызван определнный код
  • Расширение существующего функционала для добавление новых данных или уже существующих

Введение

С общими данными связанными с созданием, установкой и работой дополнения можно ознакомиться в этой документации

Расширение существующего функционала API

Расширения существующей точки входа API производится с помощью вызова статического метода extendApiResponse класса bff\extend\ApiExtensionsHooks в методе start() класса дополнения. Метод extendApiResponse использует два аргумента: название точки входа API, которое можно получить из документации к API в описании каждой точки входа API и функцию обратного вызова (callback), которая в качестве входных параметров принимает все или определенные данные расширяемого функционала api и возвращает в виде ассоциативного массива данные которые мы бы хотели встроить в конечный ответ API

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        bff\extend\ApiExtensionsHooks::extendApiResponse('bbs.items.view', function($data){
                return ['full_name' => $data['name'] . ' ' . $data['lastname']];
            });
    }
}

входе выполнения данного кода, функция обратного вызова из полученого массива данных расширяемого API функционала создаст из элементов массива с ключами name и lastname создась новый массив с ключем full_name который будет добавлен в конец исходного массива расширяемых данных API и будет выведен пользователю

Для иницилизации расширения функционала API, после метода extendApiResponse, нужно по цепочке вызвать метод build()

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        bff\extend\ApiExtensionsHooks::extendApiResponse('bbs.items.view', function($data){
                return ['full_name' => $data['name'] . ' ' . $data['lastname']];
            })->build();
    }
}

Для того чтобы получить на вход функции обратного вызова передаваемой в метод extendApiResponse, массив только определенных данных, можно использовать метод addParamsKeys(), принимающий на вход массив ключей данных для функции обратного вызова. Метод addParamsKeys() должен вызываться по цепочке после метода extendApiResponse перед методом build()

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        bff\extend\ApiExtensionsHooks::extendApiResponse('bbs.items.view', function($data){
                return ['full_name' => $data['name'] . ' ' . $data['lastname']];
            })->addParamsKeys(['name', 'lastname'])->build();
    }
}

Для добавления данных в определенное место итогового JSON ответа API, например во вложенный объект, существует метод addInsertKey() принимающий в качестве аргумента строку с перечислением через точку ключей вложенного объекта или массива, в конец которых будут добавлены данные для расширения ответа API. Например если в качестве ключа будет укзан user

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        bff\extend\ApiExtensionsHooks::extendApiResponse('bbs.items.view', function($data){
                return ['full_name' => $data['name'] . ' ' . $data['lastname']];
            })->addParamsKeys(['name', 'lastname'])->addInsertKey('user')->build();
    }
}

то данные с ключем full_name будут добавлены в конец объекта user

{
    "result": {
        "message": "success",
        "data": {
            "id": "9",
            "name": "John",
            "user": {
                "id": "1",
                "name": "John",
                "lastname": "Snow",
                "phones": [
                    {
                        "v": "0512370437",
                        "m": "05x xxx xxxx"
                    },
                    {
                        "v": "0888132137",
                        "m": "08x xxx xxxx"
                    }
                ],
                "contacts": {
                    "skype": "login"
                },
                "email": "user@example.com",
                "full_name": "John Snow"
            }
        }
    }
}

если же в метод addInsertKey() передать в качестве ключа параметр user.contacts, то данные с ключем full_name будут добавлены в конец объекта contacts находящегося в объекте user

{
    "result": {
        "message": "success",
        "data": {
            "id": "9",
            "name": "John",
            "user": {
                "id": "1",
                "name": "John",
                "lastname": "Snow",
                "phones": [
                    {
                        "v": "0512370437",
                        "m": "05x xxx xxxx"
                    },
                    {
                        "v": "0888132137",
                        "m": "08x xxx xxxx"
                    }
                ],
                "contacts": {
                    "skype": "login",
                    "full_name": "John Snow"
                },
                "email": "user@example.com"
            }
        }
    }
}

Добавление новой точки входа (endpoint) API

Чтобы реализовать новую конечную точку API, для начала необходимо определить роуты. Для реализации роутинга API дополнения предназначен класс \bff\extend\ApiRouteHooks, данный класс содержит статические методы get(), post(), put(), patch(), delete(), что соотвествуют основным методам HTTP запросов которые использует API дополнений. В качестве первого аргумента метод роутинга принимает URL, по которому можно будет вызывать конечную точку API дополнения, вторым аргументом может быть массив параметров роута

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/status', [
                                'name' => 'plugin.items.view',
                                'callback' => 'plugins\user_online_do\api\UserOnlineApi@pluginEndpoint',
                                'auth' => true
                            ]);
    }
}

API роут может иметь следующие параметры:

  • name - имя роута
  • auth - параметр определяет требуется ли авторизация для доступа к данному роуту, принимает значения true или false
  • callback - метод который будет вызван, должно быть указано название класса и метода разделенные знаком @ например: ИмяКласса@имяМетода

Если callback не был указан в параметрах роута, тогда в качестве третьего параметра в метод роутинга класса \bff\extend\ApiRouteHooks должна быть передана функция обратного вызова:

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/status', [
                                'name' => 'plugin.items.view',
                                'auth' => true
                            ], function ($api){ return ['status' => 'online']; });
    }
}

Функция обратного вызова всегда в качестве первого аргумента получает экземпляр базового класса API который содержит ряд вспомогательных методов.

В URL роута можно указывать дополнительный параметры которые должны быть выделены фигурными скобками, эти парамеиры будут переданы в фукнцию обратного вызова

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/{userId}/status/{statusId}', [
                                'name' => 'plugin.items.view',
                                'auth' => true
                            ], 
                            function ($api, $userId, $statusId){ 
                                    return [
                                            'status' => 'online',
                                            'status_id' => $statusId,
                                            'user_id' => $userId
                                        ]; 
                            });
    }
}

Также для упрощения кода вторым параметром в методы роутинга класса \bff\extend\ApiRouteHooks может быть передана функция обратного вызова, в таком случае нет необходимости передавать массив параметров роута

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/{userId}/status/{statusId}',
                            function ($api, $userId, $statusId){ 
                                    return [
                                            'status' => 'online',
                                            'status_id' => $statusId,
                                            'user_id' => $userId
                                        ]; 
                            });
    }
}

Методы класса Api

getUser()

метод getUser() возвращает id текущего авторизованного пользователя или 0 если пользователь не авторизовался

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/status',
                            function ($api){
                                    $userId = $api->getUser();
                                    $data = \Users::model()->userData($userId, ['last_activity']);
                            
                                    return [
                                            'status' => 'online',
                                            'last_activity' => $data['last_activity']
                                        ]; 
                            });
    }
}

paramsAll()

Метод paramsAll() возвращает параметры переданные с помощью запроса HTTP если они есть, и принимает в качестве первого параметра массив имен параметров HTTP запроса которые необходимо получить, вторым параметром являются значения по-умолчанию в случае если какой-либо из параметров не был передан через HTTP запрос

<?php
class Plugin_Example extends Plugin
{
    protected function start()
    {
        \bff\extend\ApiRouteHooks::get('/plugin/users/status',
                            function ($api){
                                    $params = $api->paramsAll(['user_id', 'status'], ['user_id' => 1, 'status' => 'online']);
                                    $data = \Users::model()->userData( $params['user_id'], ['last_activity']);
                            
                                    return [
                                            'status' => 'online',
                                            'last_activity' => $data['last_activity']
                                        ]; 
                            });
    }
}

Рекомендации

Структура кода

Чтобы избежать ситуации когда метод start() будет содержать слишком большое количество кода рекомендуется вынести код API дополнения в дополнительный класс и поместить его в отдельную директорию, которую можно назвать api, а в методе start() просто сделать вызов

Структура данных ответа

Если предполагается, что ответ точки входа API плагина будет содержать большой объем данных, либо будет необходимо использовать пагинацию, то для удобного структурирования ответа целесообразно использовать расширения предоставлящие возможность трансформировать данные ответа например Fractal, ознакомиться с его документацией можно тут