Битрикс24 PHP SDK как замена CRest для локальных и тиражных решений
Привет! Меня зовут Сергей Востриков, я руковожу направлением Маркетплейс и интеграций в Битрикс. Иными словами, я помогаю развивать функционал Битрикс24, доступный для разработчиков тиражных решений и индивидуальных кастомизаций. Это значит REST API и всё «вокруг» него — документацию, витрину Битрикс24 Маркет, кабинет разработчика решений и т.д.
В этой статье я расскажу о нашем новом официальном SDK для PHP и на примерах покажу его преимущества перед привычной для многих библиотекой CRest. Несмотря на её простоту, в ряде случаев ее использование вызывает большое количество проблем. И наш B24PHPSDK как раз заточен на их быстрое решение.
B24PHPSDK опубликован на Гитхабе на условиях лицензии MIT, а значит, вы смело можете использовать его в своих проектах.
Для начала напомню, как выглядит использование CRest для выполнения запроса к REST API Битрикс24. Это довольно простой код, в котором вызывается метод CRest::Call с двумя параметрами: названием нужного метода REST API и с массивом параметров этого метода.
<?php
require_once('crest.php'); // подключение CRest
// выполнение запроса к REST API
$result = CRest::call(
'crm.deal.add',
[
'fields' => [
'TITLE' => 'New Deal',
'TYPE_ID' => 'SALE',
'STAGE_ID' => 'NEW'
]
]
);
// Обработка ответа от Битрикс24
if ($result['error']) {
echo 'Error: '.$result['error_description'];
} else {
print_r($result['result']);
}
Именно за счет такой простоты CRest стал исключительно популярным способом работы с REST API Битрикс24. Он вполне подходит для простых локальных интеграций и небольших индивидуальных кастомизаций Битрикс24.
В случае с Битрикс24 PHP SDK аналогичный код будет выглядеть несколько иначе:
<?php
declare(strict_types=1);
// Подключение базового класса SDK
use Bitrix24\SDK\Services\ServiceBuilderFactory;
// следите за правильностью пути к autoload.php. он может быть другим, если
// вы используете свою структуру папок
require_once 'vendor/autoload.php';
$B24 = ServiceBuilderFactory::createServiceBuilderFromWebhook(
'--сюда надо вставить код вашего вебхука--'
);
$result = $B24->getCRMScope()->deal()->add([
'TITLE' => 'New Deal',
'TYPE_ID' => 'SALE',
'STAGE_ID' => 'NEW'
])->getId();
Здесь мы уже не передаем название метода REST API в виде параметра, а обращаемся к соответствующему методу-обертке на уровне класса PHP — в этом и состоит ключевая разница между этими библиотеками.
Рассмотрим плюсы и минусы обеих библиотек
Плюсы и минусы CRest
Этой библиотекой очень легко начать пользоваться даже человеку, который не очень глубоко знаком с разработкой ПО. Если он понимает, какой метод REST ему нужен, и при этом он внимательно ознакомился с документацией, то легко сможет сделать вызов REST API.
Но есть и минусы. Разработчик сам должен следить за правильностью параметров и их форматов. Очень легко будет ошибиться, имея дело с параметрами типа дата и время, которые в REST API передаются в виде строки определенного формата. Ещё проще сделать ошибку в обязательных параметрах или в порядке их передачи.
Простота CRest никак не помогает разобраться с такими проблемами.
Ещё больше сложностей может возникнуть, если использовать CRest в качестве библиотеки для тиражных приложений. Это реально, но на практике потребует решения многих смежных вопросов, которые функционалом CRest не закрываются.
Именно эти проблемы подтолкнули нас на публикацию нового Битрикс24 PHP SDK — гораздо более серьёзного и изначально предназначенного для решения многих задач, связанных с разработкой решений для Битрикс24.
Преимущества B24PHPSDK
автокомплит методов и структур данных в наиболее популярных IDE, таких как VSCode, PHPStorm и PyCharm. Это экономит время на кодировании.
конвертация типов данных REST API к соответствующим типам данных PHP, что защищает от ошибок и позволяет более эффективно использовать средства языка
поддержка batch-методов для получения объёмных данных с экономным использованием памяти,
поддержка лимитов REST API,
генерация и проброс уникального идентификатора каждого запроса в сторону Битрикс24 для упрощения отладки, взаимодействия приложения и очереди событий Битрикс24 и многое другое.
Разберем отличия на практике
Давайте рассмотрим несложный пример использования Битрикс24 PHP SDK, чтобы понять, как работает эта библиотека и чем отличается от CRest. Разберём решение простой задачи на базе входящих вебхуков.
Начнём с установки SDK. Вам понадобятся PHP, доступный для вызова из командной строки, и Composer (https://getcomposer.org/download/) — специальный менеджер зависимостей.
Заведём новый проект в IDE, скачаем пример и развернём его в каталоге проектов
<?php
declare(strict_types=1);
use Bitrix24\SDK\Services\ServiceBuilderFactory;
require_once 'vendor/autoload.php';
$B24 = ServiceBuilderFactory::createServiceBuilderFromWebhook(
'PUT-YOUR-WEBHOOK-URL-HERE'
);
print_r($B24->core->call('user.current')->getResponseData()->getResult());
Как вы можете видеть, это лишь один файл app.php, и никакой SDK в пример не входит.
Для того, чтобы установить его, откроем окно терминала внутри среды или за её пределами (главное, чтобы в терминале вы находились в папке проекта).
Выполните команду "composer require bitrix24/b24phpsdk". В результате у вас в проекте появится папка vendor, внутри которой уже есть все файлы SDK внутри папки bitrix24, а также ряд других полезных библиотек, используемых самим SDK.
Теперь перейдём Битрикс24, создадим входящий вебхук в разделе “Разработчикам”, дадим ему право на доступ к скоупам CRM и Пользователи.
Скопируем путь к вебхуку и вставим его внутри загруженного примера в качестве параметра метода createServiceBuilderFromWebhook.
<?php
declare(strict_types=1);
use Bitrix24\SDK\Services\ServiceBuilderFactory;
require_once 'vendor/autoload.php';
$B24 = ServiceBuilderFactory::createServiceBuilderFromWebhook(
'https://restapi.bitrix24.ru/rest/1/j2dwkksqitu62bhy/'
);
print_r($B24->core->call('user.current')->getResponseData()->getResult());
Чтобы убедиться в работоспособности кода, снова откроем терминал и запустим локальный веб сервер командой “php -S 127.0.0.1:8090”
В ответ мы видим сообщение об успешном запуске.
Локальный PHP нам нужен был не только для выполнения Composer, но и в качестве готового веб-сервера для разработки.
Откроем наш app.php в браузере (http://127.0.0.1:8090/app.php) и убедимся, что код работает — Битрикс24 должен вернуть данные пользователя, создавшего вебхук.
А теперь вернемся в IDE и разберём отличия B24PHPSDK и CRest
Как вы видите, в нашем коде мы вызывали метод REST API user.current, обращаясь к SDK.
Кажется, что код не сильно отличается от использования библиотеки CRest. Где же обещанные бонусы и преимущества?
На самом деле, в примере выше я лишь показал, что можно обращаться к произвольному методу API с помощью новой SDK. Такой способ может пригодиться, если вам нужно вызвать метод, который ещё не покрыт соответствующими обёртками на уровне SDK.
Однако, если говорить об основных методах, то SDK лучше использовать другим способом. Разберем на примере получения списка сделок из CRM.
Для этого нам нужно обратиться к методу CRMScope, затем к сделкам.
Обратите внимание, среда уже нам подсказывает, какие операции для сделок доступны. Выберем метод list и среда подскажет нам нужные параметры.
Все полученные результаты сохраняем в переменную deals и напишем небольшой цикл для вывода данных
<?php
…
$deals = $B24->getCRMScope()->deal()->list([], [], ['TITLE', 'OPPORTUNITY', 'BEGINDATE'])->getDeals();
foreach ($deals as $deal) {
echo '<p>title: '.$deal->TITLE.'<br/>';
echo 'amount: '.$deal->OPPORTUNITY.'</p>';
}
Важно: метод getDeals() вернул нам не просто массив значений, а массив объектов. Именно поэтому мы в своём коде можем удобно обращаться к конкретным полям, и среда нам их подсказывает.
Вернёмся в браузер и обновим страницу. Мы получим список нужных нам данных и сделок.
Как работать с конвертацией типов данных
Давайте посмотрим, как удобно работать с полями типа Data с помощью SDK.
У нас в списке полей сделок уже есть поле BEGINDATE, так что теперь ниже, в цикле вывода, воспользуемся подстрочной подсказкой, чтобы вывести день недели, когда началась очередная сделка. Сначала просто выведем дату:
<?php
…
$deals = $B24->getCRMScope()->deal()->list([], [], ['TITLE', 'OPPORTUNITY', 'BEGINDATE'])->getDeals();
foreach ($deals as $deal) {
echo '<p>title: '.$deal->TITLE.'<br/>';
echo 'day of week: '.$deal->BEGINDATE.'<br/>';
echo 'amount: '.$deal->OPPORTUNITY.'</p>';
}
Всё получилось. Теперь же мы можем вывести это поле, но не просто как полное значение, а сразу обратившись к функции dayOfWeek. Она доступна нам как раз потому, что SDK уже за нас с вами сконвертировал это поле в дату — это хорошо видно по значениям подстрочной подсказки.
Запустим пример и убедимся, что всё работает. В случае с CRest пришлось бы самостоятельно заниматься проверкой и преобразованием значений. Например, пришлось бы сообразить какую-то функцию вроде
<?
function getDayOfWeek($dateString) {
$dateTime = DateTime::createFromFormat('Y-m-d', $dateString);
if ($dateTime !== false) {
return $dateTime->format('N');
} else {
return 'Invalid date format';
}
}
и вызывать её самостоятельно для каждого значения полей типа Дата у каждой сделки. Не то, чтобы это очень сложно. Просто неудобно.
Работа с batch-методами
Использование метода batch исключительно полезно с точки зрения улучшения производительности приложения и для того, чтобы реже сталкиваться с лимитами по REST API.
Используем стандартную для языка конструкцию цикла, при этом для получения данных обратимся к конструкции batch, далее к методу list, с которым уже знакомы.
Добавим лишь значение параметра LIMIT, чтобы ограничить выборку 10000 сделок.
<?
…
foreach ($B24->getCRMScope()->deal()->batch->list([], [],
['TITLE', 'OPPORTUNITY', 'BEGINDATE'], 10000) as $deal) {
echo '<p>title: '.$deal->TITLE.'<br/>';
echo 'day of week: '.$deal->BEGINDATE->dayOfWeek().'<br/>';
echo 'amount: '.$deal->OPPORTUNITY.'</p>';
}
Под капотом здесь используется механизм генератора, что позволяет очень удобно и экономично обрабатывать такие большие массивы данных без лишних затрат памяти.
В случае с CRest никто за нас эту работу не проделает и нужно писать всю реализацию самостоятельно. Логика тут следующая:
Сначала с нашим условием фильтра мы получаем последние подходящие 50 сделок. А кроме того, узнаем, сколько вообще сделок подойдут под наш запрос. Соответственно, этот единственный запрос к методу crm.deal.list мы должны выполнить с параметром start=1. Только в этом случае Битрикс24 сообщит нам значение total в ответе.
Остальные записи мы уже можем получать с помощью batch-пакетов, максимум по 50 запросов в одном пакете. Именно это позволит нам за 1 хит вытаскивать по 2500 сделок.
Зная total, мы точно можем рассчитать, когда нам надо остановиться, формируя очередной batch-запрос. Вы увидите это в примере ниже.
Самый “хитрый” момент заключается в том, что внутри фильтра в очередном вызове crm.deal.list используется конструкция виде 'filter' => ['<ID' => '$result[deal_' . ($i - 1).'][49][ID]'], которая является макроподстановкой значения из результата предыдущего значения.
Если вам ещё не стало страшно, то вот пример
<?php
require_once DIR . '/crest.php';
$allDeals = [];
$batchSize = 50;
$continue = true;
$lastDealtId = 0;
// Первый запрос для получения общего количества сделок
$initialResponse = CRest::call('crm.deal.list', [
'order' => ['ID' => 'DESC'],
'filter' => [],
'select' => ['ID', 'TITLE', 'OPPORTUNITY', 'BEGINDATE'],
'start' => 0
]);
$allDeals = array_merge($allDeals, $initialResponse['result']);
if (!empty($initialResponse['total'])) {
$totalDeals = $initialResponse['total'];
$remainingDeals = $totalDeals - count($initialResponse['result']);
$lastDealtId = end($initialResponse['result'])['ID'];
} else {
$totalDeals = 0;
$remainingDeals = 0;
$continue = false; // Если сделок нет, выходим из цикла
}
while ($continue) {
$batch = [];
$requestsInBatch = min($batchSize, ceil($remainingDeals / 50));
$batch['deal_0'] = [
'method' => 'crm.deal.list',
'params' => [
'order' => ['ID' => 'DESC'],
'filter' => ['<ID' => $lastDealtId],
'select' => ['ID', 'TITLE', 'OPPORTUNITY', 'BEGINDATE'],
'start' => -1
]
];
for ($i = 1; $i < $requestsInBatch; $i++) {
$batch['deal_' . $i] = [
'method' => 'crm.deal.list',
'params' => [
'order' => ['ID' => 'DESC'],
'filter' => ['<ID' => '$result[deal_' . ($i - 1).'][49][ID]'],
'select' => ['ID', 'TITLE', 'OPPORTUNITY', 'BEGINDATE'],
'start' => -1
]
];
}
$result = CRest::callBatch($batch, false);
$gotAny = false;
if (!empty($result['result']['result'])) {
foreach ($result['result']['result'] as $cmdResult) {
if (!empty($cmdResult)) {
$gotAny = true;
$allDeals = array_merge($allDeals, $cmdResult);
}
}
}
// Обновляем ID последнего обработанного элемента
if (!empty($result['result']['result'])) {
$lastDealList = end($result['result']['result']);
$lastDeal = end($lastDealList);
$lastDealtId = $lastDeal['ID'];
}
$remainingDeals -= $requestsInBatch * 50;
$continue = $gotAny && $remainingDeals > 0;
}
echo 'Получено сделок: ' . count($allDeals) . PHP_EOL;
Согласитесь, вариант с
foreach ($B24->getCRMScope()->deal()->batch->list([], [],
['TITLE', 'OPPORTUNITY', 'BEGINDATE'], 10000) as $deal) ...
кардинально нагляднее. Не говоря уже о том, что, повторюсь, в этом случае “под капотом” используется генератор, а значит, мы можем экономно расходовать память внутри цикла. Конечно, никто не мешает проделать такое же упражнение с CRest, но это уж вы как-нибудь сами, пожалуйста.
В общем и целом, остаётся только добавить, что все наши дальнейшие планы связаны только с развитием B24PHPSDK. Именно этот SDK и дальше будет использоваться в шаблонах готовых приложений, запланированных к публикации в этом году.
CRest же хорош только в случае, когда вам надо быстренько подпилить какую-то мелкую поделку на базе парочки методов REST Битрикс24 и не хочется ради этого тащить на сервер кучку связанных библиотек.
Правда, в большинстве случаев практика показывает, что такие дерзновенные начала почти всегда приводят к тому, что к паре методов добавляются ещё несколько десятков, потом появляется интерфейс настроек, а потом нужна входная очередь для обработки REST-событий и так далее. Стоит ли экономия на файлах риска потом сильно переделывать свой код - каждый решает за себя.