Extending API with Add-ons
Main Goal
Add-ons are designed to extend/change the functionality of the API without modifying the core code. You can easily create your own add-on. Within an add-on, you have the following options:
- Adding a new API endpoint. An API endpoint is a URL such as
/shops/items/1
, where a specific code is executed when a request is made to this URL. - Extending existing functionality to add new or existing data.
Introduction
You can find general information related to creating, installing, and working with add-ons in this documentation.
Extending Existing API Functionality
To extend an existing API endpoint, you need to call the static method extendApiResponse
of the class bff\extend\ApiExtensionsHooks
in the start()
method of your add-on. The extendApiResponse
method takes two arguments: the name of the API endpoint, which can be obtained from the API documentation in the description of each API endpoint, and a callback function that takes all or specific data of the extendable API functionality as input parameters and returns the data we want to embed in the final API response as an associative array.
<?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']];
});
}
}
In the above code, the callback function will create a new array with the key full_name
from the elements of the extendable API data array with the keys name
and lastname
, and this new array will be added to the end of the original extendable API data array and displayed to the user.
To initialize the API functionality extension, you need to chain the build()
method after the extendApiResponse
method.
<?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();
}
}
To pass only specific data to the callback function passed to the extendApiResponse
method, you can use the addParamsKeys()
method, which takes an array of data keys for the callback function as input. The addParamsKeys()
method should be chained after the extendApiResponse
method and before the build()
method.
<?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();
}
}
To add data to a specific location in the final JSON API response, such as a nested object, the addInsertKey()
method takes as an argument a string listing the dotted keys of the nested object or array to the end of which data will be added to extend the API response. For example, if user
is specified as a key.
<?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();
}
}
then the data with the full_name
key will be added to the end of the user
object
{
"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"
}
}
}
}
If the user.contacts
parameter is passed to the addInsertKey()
method as a key, the data with the full_name
key will be added to the end of the contacts
object located in the user
object.
{
"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"
}
}
}
}
Adding a New API Endpoint
To implement a new API endpoint, you need to define routes first. The \bff\extend\ApiRouteHooks
class is used to implement API routing for extensions. This class contains static methods get()
, post()
, put()
, patch()
, delete()
, which correspond to the main HTTP request methods used by extensions' APIs. The routing method takes a URL as the first argument, which can be used to call the extension's API endpoint. The second argument can be an array of route parameters.
<?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 routes can have the following parameters:
-
name
- the name of the route. -
auth
- this parameter determines whether authentication is required to access this route and accepts a value oftrue
orfalse
. -
callback
- the method that will be called. The class name and method name should be separated by the@
symbol, for example:ClassName@methodName
.
If callback
is not specified in the route parameters, a callback function should be passed as the third parameter to the routing method of the \bff\extend\ApiRouteHooks
class.
<?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']; });
}
}
The callback function always receives an instance of the base API class as its first argument, which contains a number of helper methods.
Additional parameters can be specified in the URL of the route, which should be enclosed in curly braces. These parameters will be passed to the callback function.
<?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
];
});
}
}
Alternatively, a callback function can be passed as the second parameter to the routing methods of the \bff\extend\ApiRouteHooks
class, avoiding the need to pass an array of route parameters.
<?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
];
});
}
}
Class Api
methods
getUser()
The method getUser()
returns the ID of the current authenticated user, or 0 if the user is not authenticated.
<?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()
The method paramsAll()
returns the parameters passed through the HTTP request, if any. The method accepts an array of parameter names as the first parameter, and default values as the second parameter in case any of the parameters were not passed through the HTTP request.
<?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']
];
});
}
}
Recommendations
Code structure
To avoid having start()
method contain too much code, it is recommended to extract the API code of the plugin into a separate class and place it in a separate directory, which can be named api
. Then simply call that class in the start()
method.
Response data structure
If it is expected that the response of the plugin's API endpoint will contain a large amount of data, or if pagination needs to be used, it is advisable to use extensions that provide the ability to transform response data, such as Fractal
. You can learn more about it in the documentation.