Developing Classes Using the D7 API for 1C-Bitrix
The D7 API is not merely a "new API" layered on top of the old one. It represents a different development philosophy: classes instead of global functions, namespaces instead of CModule, Result objects instead of bool and error strings, Application as the entry point instead of global variables. Developing on D7 means code that does not break when Bitrix updates, can be tested, reused, and read.
Core Concepts of the D7 API
Application — singleton, entry point:
$app = \Bitrix\Main\Application::getInstance();
$connection = $app->getConnection(); // database connection
$request = $app->getContext()->getRequest(); // HTTP request
$server = $app->getContext()->getServer(); // server data
Result objects. Any method that can fail returns a descendant of \Bitrix\Main\Result:
class MyServiceResult extends \Bitrix\Main\Result
{
private ?array $data = null;
public function setData(array $data): void
{
$this->data = $data;
}
public function getData(): ?array
{
return $this->data;
}
}
// Usage
$result = new MyServiceResult();
if ($someConditionFailed) {
$result->addError(new \Bitrix\Main\Error('No data to process', 'NO_DATA'));
return $result;
}
$result->setData($processedData);
return $result;
// Calling code
$result = MyService::process($input);
if (!$result->isSuccess()) {
foreach ($result->getErrors() as $error) {
echo $error->getMessage(); // No data to process
}
}
This eliminates the class of errors "function returned false, but the reason is unknown."
Service Class Structure
Business logic is placed in services — classes with a clear single responsibility:
namespace MyProject\Services;
use Bitrix\Main\Result;
use Bitrix\Main\Error;
use MyProject\Storage\OrderLogTable;
class OrderService
{
public function __construct(
private readonly int $orderId
) {}
public function changeStatus(string $newStatus): Result
{
$result = new Result();
$allowedStatuses = ['PENDING', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CANCELLED'];
if (!in_array($newStatus, $allowedStatuses, true)) {
$result->addError(new Error("Invalid status: {$newStatus}", 'INVALID_STATUS'));
return $result;
}
$order = \Bitrix\Sale\Order::load($this->orderId);
if (!$order) {
$result->addError(new Error("Order #{$this->orderId} not found", 'ORDER_NOT_FOUND'));
return $result;
}
$saveResult = $order->setField('STATUS_ID', $newStatus);
if (!$saveResult->isSuccess()) {
$result->addErrors($saveResult->getErrors());
return $result;
}
$order->save();
OrderLogTable::add([
'ORDER_ID' => $this->orderId,
'ACTION' => "STATUS_CHANGED_TO_{$newStatus}",
]);
return $result;
}
}
Working with HTTP Requests via D7
The old way — $_POST['field']. The D7 way:
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$field = $request->getPost('field'); // POST parameter
$param = $request->getQuery('param'); // GET parameter
$file = $request->getFile('upload'); // uploaded file
$isAjax = $request->isAjaxRequest();
$isPost = $request->isPost();
The Request object filters input data and provides typed access. This is not "magically more secure," but it makes the source of data explicit.
Service Locator and DI
Bitrix does not have a full DI container, but it has a service locator:
// Registering a service (in init.php or in a module method)
\Bitrix\Main\DI\ServiceLocator::getInstance()->addInstanceLazy(
'myproject.order.service',
static function() {
return new \MyProject\Services\OrderNotificationService(
new \MyProject\Services\MailSender(),
new \MyProject\Services\SmsGateway()
);
}
);
// Retrieving the service anywhere in the code
$notifier = \Bitrix\Main\DI\ServiceLocator::getInstance()
->get('myproject.order.service');
Moving away from global variables and static calls makes code testable.
D7 Cache
$cache = \Bitrix\Main\Data\Cache::createInstance();
$cacheKey = md5('product_list_' . $categoryId);
if ($cache->initCache(3600, $cacheKey, '/myproject/products/')) {
$data = $cache->getVars();
} else {
$data = $this->loadFromDatabase($categoryId);
$cache->startDataCache();
$cache->endDataCache($data);
}
Tagged cache for invalidating a group of records:
$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache('/myproject/products/');
$taggedCache->registerTag('my_product_' . $productId);
$taggedCache->endTagCache();
// Invalidation when a product changes
$taggedCache->clearByTag('my_product_' . $productId);
Working with Files via D7
use Bitrix\Main\IO\File;
use Bitrix\Main\IO\Directory;
// Creating a directory
Directory::createDirectory('/path/to/dir');
// Working with a file
$file = new File('/path/to/file.txt');
$file->putContents('content');
$content = $file->getContents();
$exists = $file->isExists();
Common Patterns When Developing Service Classes
Repository — a class for working with data storage, isolates ORM code from business logic.
Factory — an object factory by type or parameters.
Strategy — algorithm selection at runtime (e.g., different discount calculation methods depending on customer type).
These patterns are not tied to Bitrix — they are standard PHP. D7 simply provides enough tools to apply them.
Timeline
| Task | Timeline |
|---|---|
| A set of service classes for a specific feature (5–10 classes) | 1–2 weeks |
| Full service layer for a module (repositories, services, factories, Result objects) | 3–5 weeks |
| Writing unit tests for existing classes | +30–50% on top of development time |
Code built on the D7 API has a long lifespan. It can be updated incrementally, tested, and reused in other projects. This pays off at the very first major refactoring or when handing the project over to a new developer.







