ReferenceField — это декларативное описание JOIN на уровне модели. Вместо ручного SQL вы один раз описываете условие связи между сущностями, а дальше обращаетесь к полям связанной таблицы через точку в фильтрах, выборке и сортировке.
Что такое ReferenceField
Bitrix\Main\ORM\Fields\Relations\Reference (и его прежний алиас ReferenceField) описывает связь текущей сущности с другой. Поле объявляется в getMap() и хранит не значение, а правило соединения — целевую сущность и условие Join::on().
Минимальный пример: заказ ссылается на пользователя.
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
(new Reference(
'USER',
\Bitrix\Main\UserTable::class,
Join::on('this.USER_ID', 'ref.ID')
))->configureJoinType('inner');
После этого поля пользователя доступны как USER.NAME, USER.EMAIL — ORM сама подставит нужный JOIN.
Условия соединения Join::on
Класс Bitrix\Main\ORM\Query\Join строит условие связи. Псевдонимы this и ref обозначают текущую и связанную сущности:
Join::on('this.USER_ID', 'ref.ID')— простое равенство полей;->where('ref.ACTIVE', 'Y')— дополнительное условие на связанную таблицу;->whereColumn(...)— сравнение двух колонок;->whereIn(...)и логические группы черезwith().
Составное условие связи выглядит так:
Join::on('this.SECTION_ID', 'ref.ID')
->where('ref.ACTIVE', 'Y')
->where('ref.IBLOCK_ID', 5);
Join::on попадают в ON-часть соединения, а не в WHERE. Это важно для LEFT JOIN: условие на ref внутри on() не отсекает строки основной таблицы.Тип соединения: inner, left, right
Тип JOIN задаётся методом configureJoinType() у поля-ссылки либо автоматически подбирается запросом. По умолчанию, если в условии есть обязательные ограничения, ORM может выбрать LEFT.
| Тип | Поведение |
|---|---|
inner | Только строки, у которых есть связанная запись |
left | Все строки основной таблицы, связанные — если есть |
right | Все строки связанной таблицы |
Если поле объявлено как left, но в фильтре запроса появляется условие на USER.ACTIVE = 'Y', соединение может ужесточиться до inner — учитывайте это при отладке количества строк.
Выборка данных по связи
Обращение к связанным полям идёт через точечную нотацию в любом параметре getList(): select, filter, order, group.
$rows = OrderTable::getList([
'select' => ['ID', 'SUM', 'USER_NAME' => 'USER.NAME'],
'filter' => ['=USER.ACTIVE' => 'Y', '>SUM' => 1000],
'order' => ['USER.LAST_NAME' => 'ASC'],
])->fetchAll();
- псевдоним в
select('USER_NAME' => 'USER.NAME') задаёт ключ в результате; - цепочки связей допускают несколько уровней:
USER.GROUP.NAME, если такие reference объявлены; - ORM добавляет JOIN только когда поле связи реально используется в запросе.
select размножает строки основной сущности. Для агрегации используйте group и агрегатные функции, иначе получите повторы.Reference на лету через registerRuntimeField
Если связь нужна разово и незачем прописывать её в getMap(), объявите reference прямо в запросе через registerRuntimeField():
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
$result = OrderTable::query()
->registerRuntimeField(
'PAYMENT',
new Reference('PAYMENT', PaymentTable::class,
Join::on('this.ID', 'ref.ORDER_ID'))
)
->addSelect('PAYMENT.PAID')
->setFilter(['=PAYMENT.PAID' => 'Y'])
->exec();
Такой подход удобен, когда связь зависит от контекста (разные внешние таблицы) или сущность вам не принадлежит и править её getMap() нельзя.
Связь многие-ко-многим
«Многие-ко-многим» в Битрикс реализуется через промежуточную таблицу и два reference. Классический путь — объявить в основной сущности связь на связующую таблицу, а в ней — на целевую:
- таблица
ProductTableиTagTable; - связующая
ProductTagTableс полямиPRODUCT_ID,TAG_IDи reference на обе стороны; - в запросе идёте по цепочке
PRODUCT_TAG.TAG.NAME.
Начиная с новых версий модуля main доступна и специализированная связь ManyToMany (Bitrix\Main\ORM\Fields\Relations\ManyToMany), которая скрывает промежуточную таблицу. Но для полного контроля над схемой чаще используют явные reference на связующую сущность.
Итог
ReferenceField переносит логику соединений из SQL в декларативную модель: один раз описываете Join::on() — и обращаетесь к полям связанных таблиц через точку в фильтрах, выборке и сортировке. Помните про тип соединения, ON-условия против WHERE и размножение строк при «один-ко-многим». Runtime-reference закрывает разовые связи без правки getMap().
Мы в B2Bsite проектируем модель данных со сложными связями между сущностями каталога, заказов и учётных систем. Если ваш проект перерос простые инфоблоки и нуждается в чистой ORM-схеме с корректными join'ами, поможем спроектировать и оптимизировать архитектуру данных.
Частые вопросы
Чем ReferenceField отличается от обычного поля ORM?
ReferenceField не хранит значение, а описывает правило соединения с другой сущностью через Join::on(). При обращении к его полям ORM добавляет соответствующий JOIN в запрос.
Что означают this и ref в Join::on?
this — псевдоним текущей сущности, где объявлена связь, а ref — псевдоним связанной сущности. Через них строится условие соединения, например Join::on('this.USER_ID', 'ref.ID').
Как выбрать поле связанной таблицы?
В select, filter или order используйте точечную нотацию: 'USER.NAME'. Можно задать псевдоним результата: 'USER_NAME' => 'USER.NAME'. JOIN подставляется только если поле реально используется.
Почему LEFT JOIN отсекает строки основной таблицы?
Скорее всего условие на связанную таблицу попало в WHERE, а не в ON. Условия на ref внутри Join::on()->where() не отсекают строки, а перенос в filter запроса превращает LEFT в фактический INNER.
Как объявить связь без изменения getMap()?
Используйте registerRuntimeField() прямо в объекте Query и передайте туда объект Reference с нужным Join::on(). Такая связь живёт только в рамках конкретного запроса.
Как реализовать связь многие-ко-многим?
Через промежуточную таблицу с двумя reference на обе стороны, обращаясь по цепочке вида PRODUCT_TAG.TAG.NAME. В новых версиях main есть и специальная связь ManyToMany, скрывающая связующую таблицу.
Почему при связи один-ко-многим появляются дубли строк?
JOIN размножает строки основной сущности по числу связанных записей. Чтобы избежать повторов, применяйте group и агрегатные функции или выбирайте связанные данные отдельным запросом.
Поможем с настройкой и поддержкой Для разработчиков
Поможем с настройкой, доработкой и поддержкой Для разработчиков.