Разработка типизированного 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 дней |







