bitrix24/b24phpsdk
Bitrix24 PHP SDK for working with the Bitrix24 REST API from Laravel or plain PHP. Provides typed clients, authentication helpers, API method wrappers, pagination, and webhook/OAuth support to simplify integrating CRM, tasks, chats, and other Bitrix24 modules.
The test suite is split into two levels:
| Level | Location | Speed | Requires portal |
|---|---|---|---|
| Unit | tests/Unit/ |
Fast (in-memory) | No |
| Integration | tests/Integration/ |
Slow (HTTP) | Yes |
Unit tests use stub implementations of core interfaces (NullCore, NullBatch, NullBulkItemsReader) and never make HTTP calls. Integration tests connect to a real Bitrix24 portal via an incoming webhook.
tests/.env.local file (see below)The Makefile loads tests/.env (committed defaults) and then merges tests/.env.local (local overrides, git-ignored).
Create tests/.env.local:
BITRIX24_WEBHOOK=https://your-portal.bitrix24.com/rest/1/your-webhook-token/
Optional fields (defaults are set in tests/.env):
INTEGRATION_TEST_LOG_LEVEL=100 # Monolog level (100 = DEBUG)
LOGS_FILE=sdk.log
INTEGRATION_TEST_OPEN_LINE_CODE= # required for IMOpenLines integration tests
make test-unit
Runs the unit_tests PHPUnit suite inside Docker. No portal required.
Each integration suite maps to a specific Bitrix24 API scope. Run the one you need:
make test-integration-scope-crm
make test-integration-scope-lists
make test-integration-calendar-event
make test-integration-calendar-resource
make test-integration-im-open-lines-config
make test-integration-im-open-lines-crm-chat
make test-integration-im-open-lines-session
make test-integration-im-open-lines-operator
make test-integration-landing-page
make test-integration-landing-syspage
make test-integration-landing-repo
make test-integration-landing-demos
make test-integration-landing-role
make test-integration-sale-basket-property
make test-integration-sale-cashbox-handler
make test-integration-sale-cashbox
make test-integration-sale-delivery
make test-integration-sale-delivery-extra-service
make test-integration-sale-payment-item-basket
make test-integration-sale-payment-item-shipment
make test-integration-sale-property-relation
make test-integration-legacy-task
make test-integration-main-eventlog
docker-compose run --rm php-cli vendor/bin/phpunit tests/Integration/Services/CRM/Deal/Service/DealTest.php
docker-compose run --rm php-cli vendor/bin/phpunit tests/Integration/Services/CRM/Deal/Service/DealTest.php --filter testAdd
If a service returns an entity from get and/or list, add a separate integration test file dedicated to result-item phpdoc annotation validation.
These tests must validate both:
fields()->getFieldsDescription() are present in the result-item annotationstests/CustomAssertions/CustomBitrix24Assertions.phpUse one dedicated annotation test file per result-item class. Keep it separate from CRUD and use-case tests so annotation regressions stay isolated and easy to spot.
Recommended naming convention:
AnnotationsTestTaskItemResultAnnotationsTest.php, DealItemResultAnnotationsTest.phptestAllSystemFieldsAnnotated, testAllSystemFieldsHasValidTypeAnnotationWhen the remote API returns field codes in a naming style different from the SDK result-item properties, normalize the field keys in the test before calling the shared assertions.
Run all linters in sequence:
make lint-all
Individual linters:
| Target | Tool | Notes |
|---|---|---|
make lint-allowed-licenses |
license-checker | Validates dependency licenses |
make lint-cs-fixer |
php-cs-fixer | Check code style |
make lint-cs-fixer-fix |
php-cs-fixer | Auto-fix code style |
make lint-phpstan |
PHPStan | Static analysis |
make lint-rector |
Rector | Check upgrade rules |
make lint-rector-fix |
Rector | Auto-apply upgrade rules |
make lint-deptrac |
Deptrac | Enforce architectural layer boundaries |
Deptrac statically analyses PHP use imports and enforces
that classes only depend on layers they are allowed to.
| Layer | May depend on |
|---|---|
Core |
— (nothing inside the SDK) |
Application |
Core, Services |
Infrastructure |
Core, Services |
Services |
Core, Application, Legacy |
Legacy |
Core, Application, Services |
Rules live in deptrac.yaml at the project root.
The skip_violations section records pre-existing violations that have not been fixed yet.
Each entry carries a TODO comment describing the required refactoring.
Rule: never add a new entry to skip_violations to silence a freshly introduced violation —
fix the import instead.
make lint-deptrac # check only
Deptrac is also included in make lint-all, so it runs as part of the full quality gate.
| Field | Meaning |
|---|---|
| Violations | Imports that break a layer rule and are not skipped — must be zero |
| Skipped violations | Known pre-existing violations declared in skip_violations |
| Uncovered | Classes not assigned to any layer (vendor code, tests) — expected to be high |
| Allowed | Imports that satisfy the ruleset — informational |
skip_violationOnly allowed for violations that existed before your change.
Add an entry to deptrac.yaml → skip_violations with a TODO comment:
skip_violations:
Bitrix24\SDK\Core\MyClass:
# TODO: move FooInterface to Core so Core does not depend on Infrastructure
- Bitrix24\SDK\Infrastructure\FooInterface
| Target | Description |
|---|---|
make docker-init |
First-time setup: build images, install dependencies |
make docker-up |
Build and start containers |
make docker-down |
Stop and remove containers |
make docker-down-clear |
Stop and remove containers + volumes |
make docker-pull |
Pull images (ignores pull failures) |
make docker-restart |
Restart all containers |
| Target | Description |
|---|---|
make composer-install |
Install dependencies |
make composer-update |
Update dependencies |
make composer-dumpautoload |
Regenerate autoload |
| Target | Description |
|---|---|
make lint-all |
Run all linters sequentially |
make lint-allowed-licenses |
Check dependency licenses |
make lint-cs-fixer |
Check code style |
make lint-cs-fixer-fix |
Fix code style |
make lint-phpstan |
Static analysis |
make lint-rector |
Check refactoring rules |
make lint-rector-fix |
Apply refactoring rules |
make lint-deptrac |
Check architectural layer boundaries |
| Target | Description |
|---|---|
make test-unit |
Run all unit tests |
| Target | Suite |
|---|---|
make test-integration-scope-crm |
Full CRM scope |
| Target | Suite |
|---|---|
make test-integration-scope-lists |
Full Lists scope |
make test-integration-lists-service |
Lists service |
make test-integration-lists-field |
Lists fields |
make test-integration-lists-section |
Lists sections |
make test-integration-lists-element |
Lists elements |
| Target | Suite |
|---|---|
make test-integration-scope-im-open-lines-connector |
Connector |
make test-integration-im-open-lines-config |
Config |
make test-integration-im-open-lines-crm-chat |
CRM Chat |
make test-integration-im-open-lines-session |
Session |
make test-integration-im-open-lines-operator |
Operator |
| Target | Suite |
|---|---|
make test-integration-calendar-event |
Events |
make test-integration-calendar-resource |
Resources |
| Target | Suite |
|---|---|
make test-integration-landing-page |
Pages |
make test-integration-landing-syspage |
System pages |
make test-integration-landing-repo |
Repo |
make test-integration-landing-demos |
Demos |
make test-integration-landing-role |
Roles |
make test-integration-scope-landing-template |
Templates |
| Target | Suite |
|---|---|
make test-integration-sale-basket-property |
Basket property |
make test-integration-sale-cashbox-handler |
Cashbox handler |
make test-integration-sale-cashbox |
Cashbox |
make test-integration-sale-delivery |
Delivery |
make test-integration-sale-delivery-extra-service |
Delivery extra service |
make test-integration-sale-payment-item-basket |
Payment item basket |
make test-integration-sale-payment-item-shipment |
Payment item shipment |
make test-integration-sale-property-relation |
Property relation |
| Target | Suite |
|---|---|
make test-integration-legacy-task |
Legacy task API (v1) |
| Target | Suite |
|---|---|
make test-integration-main-eventlog |
Event log (main.eventlog.*) |
tests/
├── Unit/
│ ├── Application/ # Application layer unit tests
│ ├── Core/ # Core (HTTP client, batch, credentials) unit tests
│ ├── Filters/ # Filter unit tests
│ ├── Infrastructure/ # Infrastructure unit tests
│ ├── OpenApi/ # OpenAPI unit tests
│ ├── Services/ # Service unit tests (mirrors src/Services/)
│ └── Stubs/ # Null/stub implementations (see below)
├── Integration/
│ ├── Core/ # Core integration tests
│ ├── Legacy/ # Legacy API v1 integration tests
│ └── Services/ # Service integration tests (mirrors src/Services/)
├── CustomAssertions/ # Shared assertion traits
├── Builders/ # Test builder helpers
├── Application/ # Application-level test helpers
├── ApplicationBridge/ # Webhook bridge for app-mode tests
├── bootstrap.php # PHPUnit bootstrap (loads .env files)
├── .env # Default environment variables (committed)
└── .env.local # Local overrides with your webhook (git-ignored)
#[CoversClass(MyService::class)]
class MyServiceTest extends TestCase
{
private MyService $service;
#[\Override]
protected function setUp(): void
{
$this->service = new MyService(
new NullCore(), // no HTTP calls
new NullBatch(),
new NullLogger()
);
}
#[Test]
#[DataProvider('myDataProvider')]
public function testSomeBehavior(string $input, string $expected): void
{
$this->assertEquals($expected, $this->service->process($input));
}
public static function myDataProvider(): Generator
{
yield 'case description' => ['input', 'expected'];
}
}
Key conventions:
#[CoversClass] so coverage reports are accurate#[DataProvider] for table-driven testscreateMock() (strict MockObject) when you need to assert calls; use createStub() when you only need return valuesclass MyServiceTest extends TestCase
{
use CustomBitrix24Assertions;
private MyService $service;
#[\Override]
public function setUp(): void
{
$this->service = Factory::getServiceBuilder()->getMyService();
}
#[\Override]
public function tearDown(): void
{
// clean up entities created during the test
}
public function testAdd(): void
{
$result = $this->service->add(['TITLE' => 'Test']);
$this->assertGreaterThan(0, $result->getId());
}
}
Key conventions:
Factory::getServiceBuilder() (or Factory::getCore()) in setUp()tearDown() to keep the portal tidyCustomBitrix24Assertions for domain-specific assertionsAbstract base classes enforce interface contracts across implementations:
abstract class AbstractRepositoryInterfaceTest extends TestCase
{
abstract protected function getRepository(): MyRepositoryInterface;
public function testFind(): void { /* shared contract assertion */ }
}
class ConcreteRepositoryTest extends AbstractRepositoryInterfaceTest
{
protected function getRepository(): MyRepositoryInterface
{
return Factory::getServiceBuilder()->getConcreteRepository();
}
}
Located in tests/Unit/Stubs/, these implement core interfaces without making any HTTP calls:
| Stub | Interface | Purpose |
|---|---|---|
NullCore |
CoreInterface |
Returns empty Response objects; use in service unit tests |
NullBatch |
BatchInterface |
Returns empty batch results |
NullBulkItemsReader |
BulkItemsReaderInterface |
Returns an empty traversable |
tests/CustomAssertions/CustomBitrix24Assertions.php is a trait providing domain-specific assertions:
assertBitrix24AllResultItemFieldsAnnotated(array $fieldCodesFromApi, string $resultItemClassName) — verifies that every field returned by the API is documented in the result item's PHPDoc [@property](https://github.com/property) annotations. Use this in integration tests after fetching a real entity to catch undocumented fields.PHPUnit can't write to var/ or log files
The container runs as www-data. Fix permissions with:
docker-compose run --rm php-cli chown -R www-data:www-data /var/www/html/var/
PHPStan cache errors after a rebase or major refactor
Clear the cache manually:
docker-compose run --rm php-cli vendor/bin/phpstan clear-result-cache
Integration tests fail with 401 / "Wrong webhook"
Your webhook has expired or was revoked. Generate a new incoming webhook in Bitrix24 (Settings → Developer resources → Incoming webhook) and update tests/.env.local.
Integration tests fail with "scope not available"
The webhook user does not have the required scope enabled. Edit the webhook in Bitrix24 and enable the missing scope (e.g., crm, lists, imopenlines).
lint-deptrac reports new violations after adding a class
A new class depends on a layer it is not allowed to use. Fix the import — do not add it to skip_violations.
Check deptrac.yaml → ruleset for what each layer is allowed to import.
lint-deptrac fails with vendor/bin/deptrac: not found
Deptrac is not installed. Run:
make composer-install
How can I help you explore Laravel packages today?