# 10. Базы данных и ORM Битрикс
[← Оглавление](./index.md)
---
## Репозиторий инкапсулирует работу с данными
**ПЛОХО — ORM-ЗАПРОСЫ РАЗБРОСАНЫ ПО СЕРВИСАМ**
```php
class DealReportService {
public function getWonDealsSum(): float {
// прямой вызов ORM из сервиса без абстракции
$result = \Bitrix\Crm\DealTable::getList([
'filter' => ['=STAGE_SEMANTIC_ID' => 'S'],
'select' => ['OPPORTUNITY_SUM'],
'runtime' => [
new \Bitrix\Main\Entity\ExpressionField(
'OPPORTUNITY_SUM',
'SUM(%s)',
'OPPORTUNITY'
)
]
])->fetch();
return (float)($result['OPPORTUNITY_SUM'] ?? 0);
}
}
```
**ХОРОШО — ПАТТЕРН REPOSITORY**
```php
interface DealRepository {
public function getTotalWonOpportunity(): float;
}
class OrmDealRepository implements DealRepository {
public function getTotalWonOpportunity(): float {
$result = \Bitrix\Crm\DealTable::query()
->addSelect(
new \Bitrix\Main\Entity\ExpressionField(
'SUM_OPP',
'SUM(%s)',
'OPPORTUNITY'
)
)
->where('STAGE_SEMANTIC_ID', 'S')
->exec()->fetch();
return (float)($result['SUM_OPP'] ?? 0);
}
}
class DealReportService {
public function __construct(private DealRepository $dealRepo) {}
public function getWonDealsSum(): float {
return $this->dealRepo->getTotalWonOpportunity();
}
}
```
---
## Транзакции для согласованности данных
**ПЛОХО — НЕТ ТРАНЗАКЦИИ, ДАННЫЕ МОГУТ РАССОГЛАСОВАТЬСЯ**
```php
\Bitrix\Crm\DealTable::update($dealId, ['STAGE_ID' => 'WON']);
\Bitrix\Crm\InvoiceTable::add([/* ... */]); // если упадёт — сделка уже обновлена
```
**ХОРОШО — АТОМАРНОЕ ОБНОВЛЕНИЕ С ROLLBACK**
```php
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
\Bitrix\Crm\DealTable::update($dealId, ['STAGE_ID' => 'WON']);
\Bitrix\Crm\InvoiceTable::add([/* ... */]);
$connection->commitTransaction();
} catch (\Exception $e) {
$connection->rollbackTransaction();
throw $e;
}
```
---
## Решение проблемы N+1 через JOIN в ORM
**ПЛОХО — N+1 ЗАПРОСОВ**
```php
$deals = \Bitrix\Crm\DealTable::getList(['select' => ['ID','CONTACT_ID']])->fetchAll();
foreach ($deals as $deal) {
// N дополнительных запросов!
$contact = \Bitrix\Crm\ContactTable::getByPrimary($deal['CONTACT_ID'])->fetch();
}
```
**ХОРОШО — ПРЕДЗАГРУЗКА ЧЕРЕЗ REGISTERRUNTIMEFIELD**
```php
$deals = \Bitrix\Crm\DealTable::query()
->setSelect(['ID', 'TITLE', 'CONTACT.NAME', 'CONTACT.LAST_NAME'])
->registerRuntimeField(
new \Bitrix\Main\Entity\ReferenceField(
'CONTACT',
\Bitrix\Crm\ContactTable::class,
\Bitrix\Main\ORM\Query\Join::on('this.CONTACT_ID', 'ref.ID'),
)
)
->where('STAGE_ID', 'EXECUTING')
->setOrder(['DATE_CREATE' => 'DESC'])
->exec()->fetchCollection();
foreach ($deals as $deal) {
$name = $deal->get('CONTACT.NAME'); // уже загружен, доп. запросов нет
}
```
---
## fetchCollection() и работа с объектами
**ПЛОХО — НЕСУЩЕСТВУЮЩИЙ МЕТОД КОЛЛЕКЦИИ**
```php
$leadCollection = \Bitrix\Crm\LeadTable::getList(['select' => ['*']])->fetchCollection();
$titles = $leadCollection->getTitleList(); // метод не существует!
```
**ХОРОШО — ГЕТТЕРЫ ЧЕРЕЗ GETALL()**
```php
$leadCollection = \Bitrix\Crm\LeadTable::query()
->setSelect(['ID', 'TITLE'])
->exec()->fetchCollection();
$titles = array_map(
fn($lead) => $lead->getTitle(),
$leadCollection->getAll(),
);
```