# 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() и работа с объектами

**ГЕТТЕРЫ ДОСТУПНЫ ЧЕРЕЗ GETALL()**
```php
$leadCollection = \Bitrix\Crm\LeadTable::query()
    ->setSelect(['ID', 'TITLE'])
    ->exec()->fetchCollection();

$titles = array_map(
    fn($lead) => $lead->getTitle(),
    $leadCollection->getAll(),
);
```