Чтобы новое API выглядело для разработчика менее пугающим и более знакомым, сохранено имя самого популярного метода: getList. Но если раньше каждый getList имел свой набор параметров и зашитое непрозрачное поведение, то теперь этот метод един для всех сущностей и подчиняется одним законам. Даже при желании у разработчика сущности сделать "костыль" в getList ничего не выйдет.
Сущность BookTable, взятая в качестве примера, не исключение. Какие параметры принимает метод BookTable::getList?
BookTable::getList(array(
'select' => ... // имена полей, которые необходимо получить в результате
'filter' => ... // описание фильтра для WHERE и HAVING
'group' => ... // явное указание полей, по которым нужно группировать результат
'order' => ... // параметры сортировки
'limit' => ... // количество записей
'offset' => ... // смещение для limit
'runtime' => ... // динамически определенные поля
));
getList всегда возвращает объект DB\Result
, из которого можно получить данные с помощью метода fetch():
$rows = array();
$result = BookTable::getList(array(
...
));
while ($row = $result->fetch())
{
$rows[] = $row;
}
Для получения сразу всех записей можно воспользоваться методом fetchAll():
$result = BookTable::getList($parameters);
$rows = $result->fetchAll();
// или совсем лаконично:
$rows = BookTable::getList($parameters)->fetchAll();
Теперь рассмотрим подробнее все параметры.
select
Параметр `select`
определяется в виде массива с именами полей сущности:
BookTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
));
// SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book
Если по каким-то причинам не устраивают оригинальные названия полей в результате, можно использовать алиасы:
BookTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
));
// SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book
В данном примере название поля `PUBLISH_DATE` заменяется на `PUBLICATION`, именно такое название будет фигурировать в результирующем массиве.
Если необходимо выбрать все поля, то можно воспользоваться символом '*':
BookTable::getList(array(
'select' => array('*')
));
При этом будут выбраны только поля ScalarField, а поля-выражения ExpressionField и отношения с другими сущностями затронуты не будут - их всегда нужно указывать явно.
Вычисляемое поле в select минуя runtime
В примере ниже показана упомянутая выше автоматическая группировка — система сама распознала, что необходимо группировать по полю PUBLISH_DATE. Подробнее такое поведение описывается в блоге разработчиков.
Если вычисляемое поле необходимо вам только в секции `select`
, как это чаще всего бывает, то секцию `runtime`
использовать необязательно: можно сэкономить время, поместив выражение сразу в select.
Система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде:
BookTable::getList(array(
'select' => array(
'runtime' => array(
new Entity\ExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
)
));
// SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book
Обратите внимание, что внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS. Таким образом, система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде.
Внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS.
В секции runtime можно регистрировать не только Expression поля, но и поля любых других типов. Механизм runtime работает таким образом, что к сущности добавляется новое поле, будто оно было описано в ней изначально в методе getMap. Но такое поле находится в зоне видимости только в рамках одного запроса - в следующем вызове getList такое поле уже будет недоступно, потребуется заново его зарегистрировать.
filter
Параметр `filter`
унаследовал формат фильтра инфоблоков:
// WHERE ID = 1
BookTable::getList(array(
'filter' => array('=ID' => 1)
));
// WHERE TITLE LIKE 'Patterns%'
BookTable::getList(array(
'filter' => array('%=TITLE' => 'Patterns%')
));
Фильтр может быть многоуровневым массивом со склейкой выражений AND/OR:
// WHERE ID = 1 AND ISBN = '9780321127426'
BookTable::getList(array(
'filter' => array(
'=ID' => 1,
'=ISBN' => '9780321127426'
)
));
// WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
BookTable::getList(array(
'filter' => array(
'LOGIC' => 'OR',
array(
// 'LOGIC' => 'AND', // по умолчанию элементы склеиваются через AND
'=ID' => 1,
'=ISBN' => '9780321127426'
),
array(
'=ID' => 2,
'=ISBN' => '9781449314286'
)
)
));
Полный список операторов сравнения, которые можно использовать в фильтре:
- = равно (работает и с массивами)
- % подстрока
- > больше
- < меньше
- @ IN (EXPR), в качестве значения передается массив или объект DB\SqlExpression
- !@ NOT IN (EXPR), в качестве значения передается массив или объект DB\SqlExpression
- != не равно
- !% не подстрока
- >< между, в качестве значения передается массив array(MIN, MAX)
- >= больше или равно
- <= меньше или равно
- =% LIKE
- %= LIKE
Пояснение по префиксам
|
Префиксы %= и =% эквивалентны, все примеры для одного подходят для второго:
'%=NAME' => 'тест' - отбор строки по LIKE (НЕ ПОДСТРОКИ)
'%=NAME' => 'тест%' - отбор записей, содержащих "тест" в начале поля NAME
'%=NAME' => '%тест' - отбор записей, содержащих "тест" в конце поля NAME
'%=NAME' => '%тест%' - отбор записей, содержащих подстроку "тест" в поле NAME
Последний вариант отличается от %NAME => тест итоговым sql-запросом. |
- == булевое выражение для ExpressionField (например, для EXISTS() или NOT EXISTS())
- !>< не между, в качестве значения передается массив array(MIN, MAX)
- !=% NOT LIKE
- !%= NOT LIKE
- '==ID' => null - условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)
- '!==NAME' => null - условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)
Внимание! Если не указывать явно оператор сравнения
=, то по умолчанию будет выполнен
LIKE. В данном случае использован код построения фильтров из модуля Инфоблоков, который по историческим причинам подразумевает такое поведение.
Для полей типа int ставится:
-
до выхода объектного ORM
С версии 18.0.3 модуля main.
: = (сравнение по равенству, массив разворачивается в набор условий OR =)
- после выхода: IN().
group
В параметре `group`
перечисляются поля для группировки:
BookTable::getList(array(
'group' => array('PUBLISH_DATE')
));
В подавляющем большинстве случаев явно указывать группировку не требуется - система автоматически сделает это. Подробнее смотрите ниже в секции про динамически определенные поля.
order
Параметр `order`
позволяет указать порядок сортировки:
BookTable::getList(array(
'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
));
BookTable::getList(array(
'order' => array('ID') // направление по умолчанию - ASC
));
offset/limit
Параметры `offset`
и `limit`
помогут ограничить количество выбираемых записей или реализовать постраничную выборку:
// 10 последних записей
BookTable::getList(array(
'order' => array('ID' => 'DESC')
'limit' => 10
));
// 5-я страница с записями, по 20 на страницу
BookTable::getList(array(
'order' => array('ID')
'limit' => 20,
'offset' => 80
));
runtime
Упоминаемые в 1-й части вычисляемые поля (ExpressionField) часто нужны не столько в описании сущности, сколько при выборке для различных вычислений с группировкой.
Самый простой пример, подсчет количества записей в сущности, можно выполнить следующим образом:
BookTable::getList(array(
'select' => array('CNT'),
'runtime' => array(
new Entity\ExpressionField('CNT', 'COUNT(*)')
)
));
// SELECT COUNT(*) AS CNT FROM my_book
В данном примере вычисляемое поле не просто преобразовывает значение какого-то поля, а реализует произвольное SQL выражение с функцией COUNT.
После регистрации поля в секции `runtime`
, на него можно ссылаться не только в секции `select`
, но и в других секциях:
BookTable::getList(array(
'select' => array('PUBLISH_DATE'),
'filter' => array('>CNT' => 5),
'runtime' => array(
new Entity\ExpressionField('CNT', 'COUNT(*)')
)
));
// выбрать дни, в которые выпущено более 5 книг
// SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5
Примечание. В данном примере показана упомянутая выше автоматическая группировка — система сама распознала, что необходимо группировать по полю PUBLISH_DATE. Подробнее такое поведение описывается
здесь.
Если вычисляемое поле необходимо только в секции `select`
(как это чаще всего бывает), то секцию `runtime`
использовать необязательно: можно сэкономить время, поместив выражение сразу в `select`
.
BookTable::getList(array(
'select' => array(
new Entity\ExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
)
));
// SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book
Обратите внимание, что внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS. Таким образом, система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде.
В секции `runtime`
можно регистрировать не только Expression поля, но и поля любых других типов. Механизм `runtime`
работает таким образом, что к сущности добавляется новое поле, будто оно было описано в ней изначально в методе `getMap`. Но такое поле находится в зоне видимости только в рамках одного запроса - в следующем вызове getList такое поле уже будет недоступно, потребуется заново его зарегистрировать.
Кеширование выборки
Доступно кеширование конкретной выборки с версии 16.5.9 . В самой сущности ничего не надо описывать. По умолчанию не кешируется.
В getList, в параметры добавился ключ cache
:
$res = \Bitrix\Main\GroupTable::getList(array("filter"=>array("=ID"=>1), "cache"=>array("ttl"=>3600)));
То же самое реализуется и с помощью Query:
$query = \Bitrix\Main\GroupTable::query();
$query->setSelect(array('*'));
$query->setFilter(array("=ID"=>1));
$query->setCacheTtl(150);
$res = $query->exec();
Возможно, что в результате кешированной выборки придет объект ArrayResult.
По умолчанию выборки с JOIN не кешируются. Но, если вы уверены в том, что делаете, можно явно закешировать:
"cache"=>array("ttl"=>3600, "cache_joins"=>true);
//or
$query->cacheJoins(true);
Сброс кеша происходит в любом методе add/update/delete. Принудительный сброс кеша для таблицы:
/* Пример для таблицы пользователей */
\Bitrix\Main\UserTable::getEntity()->cleanCache();
Администратору проекта доступен запрет кеширования или изменение TTL.