Свой модуль для Битрикс24 с REST API
Готовый код можно скачать в моем репозитории на GitFlic.
Cоздадим модуль, который в момент инсталяции подключится к системному событию OnRestServiceBuildDescription и через него объявит новый scope, логическую группу со списком собственных методов, более подробно почитать про добавление своих методов:
hmarketing.rest/install/index.php<?php
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// пространство имен для управления (регистрации/удалении) модуля в системе/базе
use Bitrix\Main\ModuleManager;
// пространство имен для работы с параметрами модулей хранимых в базе данных
use Bitrix\Main\Config\Option;
// пространство имен с абстрактным классом для любых приложений, любой конкретный класс приложения является наследником этого абстрактного класса
use Bitrix\Main\Application;
// пространство имен для работы c ORM
use \Bitrix\Main\Entity\Base;
// пространство имен для автозагрузки модулей
use \Bitrix\Main\Loader;
// пространство имен для событий
use \Bitrix\Main\EventManager;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
/**
* Class Hmarketing_Rest
*/
class Hmarketing_Rest extends CModule
{
// переменные модуля
public $MODULE_ID;
public $MODULE_VERSION;
public $MODULE_VERSION_DATE;
public $MODULE_NAME;
public $MODULE_DESCRIPTION;
public $PARTNER_NAME;
public $PARTNER_URI;
public $SHOW_SUPER_ADMIN_GROUP_RIGHTS;
public $MODULE_GROUP_RIGHTS;
public $errors;
// конструктор класса, вызывается автоматически при обращение к классу
function __construct()
{
// создаем пустой массив для файла version.php
$arModuleVersion = array();
// подключаем файл version.php
include_once(__DIR__ . '/version.php');
// версия модуля
$this->MODULE_VERSION = $arModuleVersion['VERSION'];
// дата релиза версии модуля
$this->MODULE_VERSION_DATE = $arModuleVersion['VERSION_DATE'];
// id модуля
$this->MODULE_ID = "hmarketing.rest";
// название модуля
$this->MODULE_NAME = Loc::getMessage('MODULE_NAME');
// описание модуля
$this->MODULE_DESCRIPTION = Loc::getMessage('MODULE_DESCRIPTION');
// имя партнера выпустившего модуль
$this->PARTNER_NAME = Loc::getMessage('PARTNER_NAME');
// ссылка на рисурс партнера выпустившего модуль
$this->PARTNER_URI = Loc::getMessage('PARTNER_URI');
// если указано, то на странице прав доступа будут показаны администраторы и группы
$this->SHOW_SUPER_ADMIN_GROUP_RIGHTS = 'Y';
// если указано, то на странице редактирования групп будет отображаться этот модуль
$this->MODULE_GROUP_RIGHTS = 'Y';
}
// метод отрабатывает при установке модуля
function DoInstall()
{
// глобальная переменная с обстрактным классом
global $APPLICATION;
// регистрируем модуль в системе
ModuleManager::RegisterModule($this->MODULE_ID);
// создаем таблицы баз данных, необходимые для работы модуля
$this->InstallDB();
// регистрируем обработчики событий
$this->InstallEvents();
// подключаем скрипт с административным прологом и эпилогом
$APPLICATION->includeAdminFile(
Loc::getMessage('INSTALL_TITLE'),
__DIR__ . '/instalInfo.php'
);
return true;
}
// метод отрабатывает при удалении модуля
function DoUninstall()
{
// глобальная переменная с обстрактным классом
global $APPLICATION;
// удаляем таблицы баз данных, необходимые для работы модуля
$this->UnInstallDB();
// удаляем обработчики событий
$this->UnInstallEvents();
// удаляем файлы, необходимые для работы модуля
$this->UnInstallFiles();
// удаляем регистрацию модуля в системе
ModuleManager::UnRegisterModule($this->MODULE_ID);
// подключаем скрипт с административным прологом и эпилогом
$APPLICATION->includeAdminFile(
Loc::getMessage('DEINSTALL_TITLE'),
__DIR__ . '/deInstalInfo.php'
);
return true;
}
// метод для создания таблицы баз данных
public function InstallDB()
{
// подключаем модуль для того что бы был видем класс ORM
Loader::includeModule($this->MODULE_ID);
// через класс Application получаем соединение по переданному параметру, параметр берем из ORM-сущности (он указывается, если необходим другой тип подключения, отличный от default), если тип подключения по умолчанию, то параметр можно не передавать. Далее по подключению вызываем метод isTableExists, в который передаем название таблицы полученное с помощью метода getDBTableName() класса Base
if (!Application::getConnection(\Hmarketing\Rest\Models\DataTable::getConnectionName())->isTableExists(Base::getInstance("\Hmarketing\Rest\Models\DataTable")->getDBTableName())) {
// eсли таблицы не существует, то создаем её по ORM сущности
Base::getInstance("\Hmarketing\Rest\Models\DataTable")->createDbTable();
}
return true;
}
// метод для удаления таблицы баз данных
public function UninstallDB()
{
// подключаем модуль для того что бы был видем класс ORM
Loader::includeModule($this->MODULE_ID);
// делаем запрос к бд на удаление таблицы, если она существует, по подключению к бд класса Application с параметром подключения ORM сущности
Application::getConnection(\Hmarketing\Rest\Models\DataTable::getConnectionName())->queryExecute('DROP TABLE IF EXISTS ' . Base::getInstance("\Hmarketing\Rest\Models\DataTable")->getDBTableName());
// удаляем параметры модуля из базы данных битрикс
Option::delete($this->MODULE_ID);
return true;
}
// метод для создания обработчика событий
function InstallEvents()
{
EventManager::getInstance()->registerEventHandler(
'rest',
'OnRestServiceBuildDescription',
$this->MODULE_ID,
'\\Hmarketing\\Rest\\Handlers\\RestHandler',
'restMethodsRegistration'
);
return true;
}
// метод для удаления обработчика событий
function UnInstallEvents()
{
EventManager::getInstance()->unRegisterEventHandler(
'rest',
'OnRestServiceBuildDescription',
$this->MODULE_ID,
'\\Hmarketing\\Rest\\Handlers\\RestHandler',
'restMethodsRegistration'
);
return true;
}
}
Для каждого метода укажем в методе restMethodsRegistration() человекочитаемое название и обработчик в виде метода обертки:
hmarketing.rest/lib/Handlers/RestHandler.php<?php
namespace Hmarketing\Rest\Handlers;
use Bitrix\Main\Loader;
use Bitrix\Main\ObjectNotFoundException;
use Bitrix\Rest\RestException;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\DI\ServiceLocator;
use Psr\Container\NotFoundExceptionInterface;
Loc::loadMessages(__FILE__);
Loader::includeModule('rest');
class RestHandler
{
const SCOPE = 'hmarketing.rest';
const SERVICE = 'hmarketing.rest.service.locator';
/**
* Хендлер метод обработчика события регистрации REST методов,
* добавляет в систему перечень кастомных методов REST
* @return array[]
*/
public static function restMethodsRegistration()
{
Loc::getMessage('REST_SCOPE');
return [
self::SCOPE => [
self::SCOPE . '.add' => [__CLASS__, 'add'],
self::SCOPE . '.list' => [__CLASS__, 'list'],
self::SCOPE . '.update' => [__CLASS__, 'update'],
self::SCOPE . '.delete' => [__CLASS__, 'delete'],
]
];
}
/**
* @param $arParams - поля и значения для добавляемой сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return int
* @throws RestException
*/
public static function add($arParams, $navStart, \CRestServer $server)
{
$service = self::getService(self::SERVICE);
return $service->add($arParams, $navStart, $server);
}
/**
* @param $arParams - параметры для выборки по полям сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return array
* @throws ArgumentException
* @throws ObjectPropertyException
* @throws SystemException
*/
public static function list($arParams, $navStart, \CRestServer $server)
{
$service = self::getService(self::SERVICE);
return $service->list($arParams, $navStart, $server);
}
/**
* @param $arParams - поля сущности с значениями которые будут обновлены
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return int
* @throws RestException
*/
public static function update($arParams, $navStart, \CRestServer $server)
{
$service = self::getService(self::SERVICE);
return $service->update($arParams, $navStart, $server);
}
/**
* @param $arParams - обязательный ключ ID записи сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* * @param \CRestServer $server - объект с данными о сервере
* @return bool
* @throws RestException
*/
public static function delete($arParams, $navStart, \CRestServer $server)
{
$service = self::getService(self::SERVICE);
return $service->delete($arParams, $navStart, $server);
}
/**
* Метод возвращает сервис если такой зарегистрирован в сервис-локаторе
* или выбрасывает исключение если его там нет
* @param string $code - код сервиса
* @return mixed
* @throws RestException
* @throws ObjectNotFoundException
* @throws NotFoundExceptionInterface
*/
private static function getService(string $code)
{
if (!ServiceLocator::getInstance()->has($code)) {
throw new RestException(
json_encode(Loc::getMessage('EXCEPTION_SERVICE_LOCATOR'), JSON_UNESCAPED_UNICODE),
RestException::ERROR_METHOD_NOT_FOUND, \CRestServer::STATUS_NOT_FOUND
);
}
$meaning = \Bitrix\Main\Config\Option::get(
"hmarketing.rest",
"on-off",
"",
false
);
if ($meaning != "Y") {
throw new RestException(json_encode(Loc::getMessage('EXCEPTION_MODULE_DESCRIPTION'), JSON_UNESCAPED_UNICODE), Loc::getMessage('EXCEPTION_MODULE_ERROR'));
}
return ServiceLocator::getInstance()->get($code);
}
}
Для удобства подключим Service Locator, более подробно можно почитать тут:
hmarketing.rest/.settings.php<?php
/**
* Регистрация класса как сервиса в ServiceLocator
*/
return [
'services' => [
'value' => [
'hmarketing.rest.service.locator' => [
'className' => '\\Hmarketing\\Rest\\Services\\StorageService',
]
],
'readonly' => true,
]
];
Класс с методами который подключает Service Locator:
hmarketing.rest/lib/Services/StorageService.php<?php
namespace Hmarketing\Rest\Services;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use Bitrix\Rest\RestException;
use Hmarketing\Rest\Models\DataTable;
use Hmarketing\Rest\Interface\EntityStorage;
class StorageService implements EntityStorage
{
/**
* @param $arParams - поля и значения для добавляемой сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return int
* @throws RestException
*/
public function add($arParams, $navStart, \CRestServer $server): int
{
$originDataStoreResult = DataTable::add($arParams);
if ($originDataStoreResult->isSuccess()) {
return $originDataStoreResult->getId();
} else {
throw new RestException(
json_encode($originDataStoreResult->getErrorMessages(), JSON_UNESCAPED_UNICODE),
RestException::ERROR_ARGUMENT, \CRestServer::STATUS_OK
);
}
}
/**
* @param $arParams - параметры для выборки по полям сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return array
* @throws ArgumentException
* @throws ObjectPropertyException
* @throws SystemException
*/
public function list($arParams, $navStart, \CRestServer $server): array
{
return DataTable::getList([
'filter' => $arParams['filter'] ?: [],
'select' => $arParams['select'] ?: ['*'],
'order' => $arParams['order'] ? [$arParams['order']['by'] => $arParams['order']['direction']] : ['ID' =>
'ASC'],
'group' => $arParams['group'] ?: [],
'limit' => $arParams['limit'] ?: 0,
'offset' => $navStart ?: 0,
])->fetchAll();
}
/**
* @param $arParams - поля сущности с значениями которые будут обновлены
* @param $navStart - если в ключе start перезать число то будет использован offset
* @param \CRestServer $server - объект с данными о сервере
* @return int
* @throws RestException
*/
public function update($arParams, $navStart, \CRestServer $server): int
{
$entityId = intval($arParams['ID']);
unset($arParams['ID']);
$originDataStoreResult = DataTable::update($entityId, $arParams);
if ($originDataStoreResult->isSuccess()) {
return $originDataStoreResult->getId();
} else {
throw new RestException(
json_encode($originDataStoreResult->getErrorMessages(), JSON_UNESCAPED_UNICODE),
RestException::ERROR_ARGUMENT, \CRestServer::STATUS_OK
);
}
}
/**
* @param $arParams - обязательный ключ ID записи сущности
* @param $navStart - если в ключе start перезать число то будет использован offset
* * @param \CRestServer $server - объект с данными о сервере
* @return bool
* @throws RestException
*/
public function delete($arParams, $navStart, \CRestServer $server): bool
{
$entityId = intval($arParams['ID']);
$originDataStoreResult = DataTable::delete($entityId);
if ($originDataStoreResult->isSuccess()) {
return true;
} else {
throw new RestException(
json_encode($originDataStoreResult->getErrorMessages(), JSON_UNESCAPED_UNICODE),
RestException::ERROR_ARGUMENT, \CRestServer::STATUS_OK
);
}
}
}