Паттерн Repository в Битрикс через RepositoryInterface
Разработчики Битрикс часто работают напрямую с DataManager, размазывая логику доступа к данным по всему проекту. Это создаёт жёсткую связанность кода с ORM, затрудняет тестирование и нарушает принцип единой ответственности. В модуле main появились интерфейсы RepositoryInterface и SoftDeletableRepositoryInterface, которые позволяют строить чистую архитектуру.
Интерфейс Bitrix\Main\Repository\RepositoryInterface определяет три базовых метода:
interface RepositoryInterface
{
public function getById(mixed $id): ?EntityInterface;
public function save(EntityInterface $entity): void;
public function delete(mixed $id): void;
}
Для реализации репозитория сущность должна имплементировать Bitrix\Main\Entity\EntityInterface:
use Bitrix\Main\Entity\EntityInterface;
class Order implements EntityInterface
{
public function __construct(
private ?int $id,
private int $userId,
private string $status,
private float $amount
) {}
public function getId(): mixed
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
// Геттеры и сеттеры для остальных свойств...
}
Теперь создаём репозиторий с инкапсулированной логикой работы с БД:
use Bitrix\Main\Repository\RepositoryInterface;
use Bitrix\Main\Repository\SoftDeletableRepositoryInterface;
use Bitrix\Main\Repository\Exception\PersistenceException;
use Bitrix\Main\Entity\EntityInterface;
class OrderRepository implements RepositoryInterface, SoftDeletableRepositoryInterface
{
public function getById(mixed $id): ?Order
{
$row = OrderTable::getById($id)->fetch();
if (!$row) {
return null;
}
return new Order($row['ID'], $row['USER_ID'], $row['STATUS'], $row['AMOUNT']);
}
public function save(EntityInterface $entity): void
{
$data = [
'USER_ID' => $entity->getUserId(),
'STATUS' => $entity->getStatus(),
'AMOUNT' => $entity->getAmount(),
];
if ($entity->getId()) {
$result = OrderTable::update($entity->getId(), $data);
} else {
$result = OrderTable::add($data);
if ($result->isSuccess()) {
$entity->setId($result->getId());
}
}
if (!$result->isSuccess()) {
throw new PersistenceException('Ошибка сохранения', errors: $result->getErrors());
}
}
public function delete(mixed $id): void
{
$result = OrderTable::delete($id);
if (!$result->isSuccess()) {
throw new PersistenceException('Ошибка удаления', errors: $result->getErrors());
}
}
public function softDelete(mixed $id): void
{
OrderTable::update($id, ['DELETED' => 'Y']);
}
}
Использование в сервисном слое:
class OrderService
{
public function __construct(
private RepositoryInterface $orderRepository
) {}
public function createOrder(int $userId, float $amount): Order
{
$order = new Order(null, $userId, 'NEW', $amount);
$this->orderRepository->save($order);
return $order;
}
}
Репозиторий изолирует бизнес-логику от деталей хранения данных. Код становится тестируемым — достаточно подставить mock-репозиторий. PersistenceException предоставляет унифицированную обработку ошибок через метод getErrors().