runtime Отношения в Битрикс ORM D7
ORM D7 в Битрикс появился уже довольно давно. Очень удобно использовать одинаковые методы для запросов к БД с помощью ::getList
, просто описывать свои таблицы с помощью ::getMap
.
Например вот так можно выбрать информацию о товарах вместе с данными о доступном количестве и зарезервированном количестве + сразу же вывести и название товара.
$products = \Bitrix\Catalog\ProductTable::getList([
"select" => [
"PROD_ID" => "ID",
"NAME" => "IBLOCK_ELEMENT.NAME",
"IBLOCK_ID" => "IBLOCK_ELEMENT.IBLOCK_ID",
"TYPE"
],
"filter" => [
"!TYPE" => \Bitrix\Catalog\ProductTable::TYPE_SKU,
"IBLOCK_ELEMENT.ACTIVE" => "Y"
],
"order" => [
"QUANTITY" => "ASC",
"QUANTITY_RESERVED" => "DESC",
]
])->fetchAll();
При использовании старого API: запрос к CCatalogProduct
+ запрос к CIblockElement
мы бы получили вместо одного запроса - два. Да и нужно было бы писать гораздо больше кода для этого. Возможность работы с данными из других таблиц появилась благодаря указанию отношений в методе getMap. Но что делать если по какой то причине эта связь не указана?
В таком случае стоит воспользоваться секцией 'runtime'
в массиве параметров метода getList
.
Например так:
$dbOrders = \Bitrix\Sale\Internals\OrderTable::getList([
"select" => [
"*",
"PROPERTY_VALUE.*",
],
"filter" => [
"PROPERTY_VALUE.PROPERTY.CODE" => "LOCATION",
"!STATUD_ID" => ["C", "RS"]
],
'runtime' => [
'PROPERTY_VALUE' => [
'data_type' => \Bitrix\Sale\Internals\OrderPropsValueTable::class,
'reference' => [
'=this.ID' => 'ref.ORDER_ID',
]
],
],
"order" => [
"ID" => "ASC"
]
]);
Данный запрос выведет список заказов в статусах C и RS сразу со значением свойства LOCATION. И все это одним запросом.
Вместо ассоциативного массива внутри runtime для каждого поля можно использовать обьекты класса \Bitrix\Main\Entity\ReferenceField
. Тогда PROPERTY_VALUE
из предыдущего примера будет выглядеть так:
new \Bitrix\Main\Entity\ReferenceField(
'PROPERTY_VALUE',
'\Bitrix\Sale\Internals\OrderPropsValue',
array(
'=this.ID' => 'ref.ORDER_ID',
)
)
При добавлении \Bitrix\Main\Entity\ReferenceField
в getList
, во время формирования SQL
запроса в секцию FROM
будет добавлен JOIN
. Тип JOIN
можно указать так:
'PROPERTY_VALUE' => [
'data_type' => \Bitrix\Sale\Internals\OrderPropsValueTable::class,
'reference' => [
'=this.ID' => 'ref.ORDER_ID',
],
['join_type' => 'LEFT']
]
Рассмотрим другой пример:
$cities = \Bitrix\Sale\Location\LocationTable::getList([
"select" => ["ID"],
"filter" => [
"TYPE_ID" => 5,
"ZIP.XML_ID" => false
],
'runtime' => [
'ZIP' => [
'data_type' => \Bitrix\Sale\Location\ExternalTable::class,
'reference' => [
'=this.ID' => 'ref.LOCATION_ID',
'=ref.SERVICE_ID' => new \Bitrix\Main\DB\SqlExpression('?i', $zipExternalId),
],
['join_type' => 'LEFT']
],
]
])->fetchAll();
В этом примере будут выбраны все местоположения для которых не заполнен почтовый индекс. В этом примере стоит обратить внимание на то, что в условиях ReferenceField
есть такая конструкция '=ref.SERVICE_ID' => new \Bitrix\Main\DB\SqlExpression('?i', $zipExternalId),
. С ее помощью в запрос будет подставлен ID внешнего сервиса отвечающего за почтовые индексы. Если этого не сделать то объединение таблиц произойдет по всем доступным сервисам. При этом условие будет добавлено не в секцию WHERE
SQL запроса, а в соответствующий JOIN. Для этого примера SQL запрос будет выглядеть так:
SELECT
`sale_location_location`.`ID` AS `ID`
FROM `b_sale_location` `sale_location_location`
LEFT JOIN `b_sale_loc_ext` `sale_location_location_zip` ON `sale_location_location`.`ID` = `sale_location_location_zip`.`LOCATION_ID` AND `sale_location_location_zip`.`SERVICE_ID` = 2
WHERE `sale_location_location`.`TYPE_ID` = 5
AND (`sale_location_location_zip`.`XML_ID` IS NULL OR `sale_location_location_zip`.`XML_ID` = '')