Розробка типізованого SDK для API 1С-Бітрікс
Написання інтеграцій з 1С-Бітрікс без SDK — це постійний copy-paste коду авторизації, ручна серіалізація запитів, рядкові назви методів без автодоповнення. Через півроку проєкт перетворюється на нечитабельний клубок curl_exec() з json_decode(). Типізований SDK вирішує це: кожен метод API — окремий клас або метод з PHPDoc, автодоповнення в IDE, єдина точка конфігурації, вбудована обробка помилок.
Що таке SDK для Бітрікс
SDK у цьому контексті — це PHP-бібліотека, яка інкапсулює роботу з одним з API:
-
REST API Битрикс24 (хмарний і коробочний) — методи виду
crm.lead.add,tasks.task.update. -
API 1С-Бітрікс (сайт) — внутрішні класи
CIBlockElement,\Bitrix\Sale\Order, D7 ORM. - Webhook API — вхідні та вихідні вебхуки Битрикс24.
Найчастіше SDK розробляється для REST API Битрикс24 — ним користуються зовнішні застосунки та інтеграції.
Структура типізованого REST API SDK
src/
├── Client/
│ ├── BitrixClient.php # HTTP-клієнт (Guzzle/native curl)
│ ├── TokenStorage.php # Зберігання OAuth-токенів
│ └── RetryMiddleware.php # Повторні спроби при 503/rate limit
├── Api/
│ ├── Crm/
│ │ ├── LeadApi.php
│ │ ├── DealApi.php
│ │ ├── ContactApi.php
│ │ └── CompanyApi.php
│ ├── Tasks/
│ │ ├── TaskApi.php
│ │ └── CommentApi.php
│ └── Catalog/
│ ├── ProductApi.php
│ └── PriceApi.php
├── Dto/
│ ├── Crm/
│ │ ├── Lead.php
│ │ ├── LeadCreateRequest.php
│ │ └── LeadListRequest.php
│ └── Common/
│ ├── Pagination.php
│ └── Filter.php
├── Exception/
│ ├── BitrixApiException.php
│ ├── AuthException.php
│ └── RateLimitException.php
└── BitrixSdk.php # Точка входу / DI-контейнер
HTTP-клієнт
namespace BitrixSdk\Client;
class BitrixClient
{
private string $baseUrl;
private TokenStorage $tokenStorage;
private \GuzzleHttp\Client $http;
public function __construct(string $baseUrl, TokenStorage $tokenStorage)
{
$this->baseUrl = rtrim($baseUrl, '/') . '/rest/';
$this->tokenStorage = $tokenStorage;
$this->http = new \GuzzleHttp\Client([
'timeout' => 30,
'connect_timeout' => 10,
]);
}
public function call(string $method, array $params = []): array
{
$token = $this->tokenStorage->getAccessToken();
$url = $this->baseUrl . $method;
try {
$response = $this->http->post($url, [
'json' => array_merge($params, ['auth' => $token]),
]);
$data = json_decode($response->getBody()->getContents(), true);
if (!empty($data['error'])) {
$this->handleError($data);
}
return $data['result'] ?? $data;
} catch (\GuzzleHttp\Exception\ClientException $e) {
$statusCode = $e->getResponse()->getStatusCode();
if ($statusCode === 401) {
// Спробувати оновити токен
$this->tokenStorage->refresh();
return $this->call($method, $params);
}
throw new BitrixApiException("HTTP {$statusCode}: " . $e->getMessage(), $statusCode);
}
}
private function handleError(array $data): void
{
$errorCode = $data['error'] ?? 'UNKNOWN';
$errorDesc = $data['error_description'] ?? '';
if ($errorCode === 'QUERY_LIMIT_EXCEEDED') {
// Rate limit — затримка і повтор
throw new RateLimitException($errorDesc);
}
if (in_array($errorCode, ['NO_AUTH_FOUND', 'expired_token', 'invalid_token'])) {
throw new AuthException($errorDesc);
}
throw new BitrixApiException("{$errorCode}: {$errorDesc}");
}
/** Пакетний запит — batch */
public function batch(array $commands): array
{
$cmdParams = [];
foreach ($commands as $key => $command) {
$cmdParams[$key] = $command->toApiParam();
}
$result = $this->call('batch', ['cmd' => $cmdParams]);
return $result['result'] ?? [];
}
}
DTO та типізовані методи
Замість роботи з сирими масивами — Data Transfer Objects:
namespace BitrixSdk\Dto\Crm;
class LeadCreateRequest
{
public string $title;
public ?string $name = null;
public ?string $lastName = null;
public ?string $phone = null;
public ?string $email = null;
public string $sourceId = 'WEB';
public ?string $comments = null;
public int $responsibleId = 0;
/** @var array<string, string> */
public array $utmFields = [];
public function toApiArray(): array
{
$fields = [
'TITLE' => $this->title,
'SOURCE_ID' => $this->sourceId,
];
if ($this->name) $fields['NAME'] = $this->name;
if ($this->lastName) $fields['LAST_NAME'] = $this->lastName;
if ($this->comments) $fields['COMMENTS'] = $this->comments;
if ($this->phone) {
$fields['PHONE'] = [['VALUE' => $this->phone, 'VALUE_TYPE' => 'WORK']];
}
if ($this->email) {
$fields['EMAIL'] = [['VALUE' => $this->email, 'VALUE_TYPE' => 'WORK']];
}
foreach ($this->utmFields as $key => $value) {
$fields[$key] = $value;
}
return $fields;
}
}
class Lead
{
public int $id;
public string $title;
public ?string $name;
public ?string $lastName;
public string $status;
public float $opportunity;
public \DateTimeImmutable $dateCreate;
public static function fromApiArray(array $data): self
{
$lead = new self();
$lead->id = (int)$data['ID'];
$lead->title = $data['TITLE'];
$lead->name = $data['NAME'] ?? null;
$lead->lastName = $data['LAST_NAME'] ?? null;
$lead->status = $data['STATUS_ID'];
$lead->opportunity = (float)($data['OPPORTUNITY'] ?? 0);
$lead->dateCreate = new \DateTimeImmutable($data['DATE_CREATE']);
return $lead;
}
}
API-клас для лідів
namespace BitrixSdk\Api\Crm;
class LeadApi
{
public function __construct(private BitrixClient $client) {}
public function add(LeadCreateRequest $request): int
{
$result = $this->client->call('crm.lead.add', [
'fields' => $request->toApiArray(),
]);
return (int)$result;
}
public function get(int $id): Lead
{
$data = $this->client->call('crm.lead.get', ['id' => $id]);
return Lead::fromApiArray($data);
}
/**
* @return Lead[]
*/
public function list(array $filter = [], array $select = [], int $start = 0): array
{
$params = ['filter' => $filter, 'start' => $start];
if ($select) $params['select'] = $select;
$data = $this->client->call('crm.lead.list', $params);
return array_map(fn($item) => Lead::fromApiArray($item), $data);
}
public function update(int $id, array $fields): bool
{
return (bool)$this->client->call('crm.lead.update', [
'id' => $id,
'fields' => $fields,
]);
}
}
Точка входу SDK
namespace BitrixSdk;
class BitrixSdk
{
private BitrixClient $client;
private ?Crm\LeadApi $leadApi = null;
private ?Crm\DealApi $dealApi = null;
private ?Tasks\TaskApi $taskApi = null;
public static function create(string $webhookUrl): self
{
$sdk = new self();
$storage = new Client\WebhookTokenStorage($webhookUrl);
$sdk->client = new Client\BitrixClient($webhookUrl, $storage);
return $sdk;
}
public static function createOAuth(string $domain, string $clientId, string $clientSecret, TokenStorage $storage): self
{
$sdk = new self();
$sdk->client = new Client\BitrixClient("https://{$domain}", $storage);
return $sdk;
}
public function crm(): CrmFacade
{
return new CrmFacade($this->client);
}
public function tasks(): TasksFacade
{
return new TasksFacade($this->client);
}
}
// Використання
$sdk = BitrixSdk::create('https://company.bitrix24.ru/rest/1/webhook_token/');
$lead = new LeadCreateRequest();
$lead->title = 'Заявка з сайту';
$lead->name = 'Іван';
$lead->phone = '+79001234567';
$leadId = $sdk->crm()->leads()->add($lead);
Rate Limiting та черга
Битрикс24 обмежує запити: 2 запити/сек для хмарних порталів. При перевищенні — HTTP 503 з X-RateLimit-Reset. SDK має це обробляти:
class RetryMiddleware
{
private const MAX_RETRIES = 3;
private const RETRY_DELAYS = [1, 2, 5]; // секунди
public function handle(callable $next, string $method, array $params): array
{
for ($attempt = 0; $attempt <= self::MAX_RETRIES; $attempt++) {
try {
return $next($method, $params);
} catch (RateLimitException $e) {
if ($attempt === self::MAX_RETRIES) throw $e;
sleep(self::RETRY_DELAYS[$attempt]);
}
}
throw new BitrixApiException('Max retries exceeded');
}
}
Для масових операцій (імпорт 10 000 лідів) — використовувати batch-метод Бітрікс (до 50 команд за раз) та чергу (Laravel Queue, RabbitMQ, Redis Queue).
Терміни розробки
| Варіант | Склад | Термін |
|---|---|---|
| Базовий SDK | CRM-методи, HTTP-клієнт, DTO, обробка помилок | 8–12 днів |
| Розширений | + Tasks, каталог, webhooks, rate limiting | 14–20 днів |
| Повний SDK з тестами | + PHPUnit тести, CI/CD, packagist-пакет | 20–30 днів |







