[spoiler]
Наиболее значительное обновление - это фильтр. К старой реализации, взятой еще из инфоблоков, накопилось немало претензий - оператор LIKE по умолчанию, проблемы при проверке на NULL, нетривиальность сравнения двух полей и т.д. Поэтому был разработан новый интерфейс, решающий на данный момент все известные проблемы и трудности с фильтрацией.
Одиночные условия
Так выглядит простейший запрос:
\Bitrix\Main\UserTable::query() ->where("ID", 1) ->exec(); // WHERE `main_user`.`ID` = 1 |
\Bitrix\Main\UserTable::query() ->where("ID", "<", 10) ->exec(); // WHERE `main_user`.`ID` < 10 |
\Bitrix\Main\UserTable::query() ->whereNull("ID") ->exec(); // WHERE `main_user`.`ID` IS NULL |
\Bitrix\Main\UserTable::query() ->whereNotNull("ID") ->exec(); // WHERE `main_user`.`ID` IS NOT NULL |
whereNull($column) / whereNotNull($column) whereIn($column, $values|Query|SqlExpression) / whereNotIn($column, $values|Query|SqlExpression) whereBetween($column, $valueMin, $valueMax) / whereNotBetween($column, $valueMin, $valueMax) whereLike($column, $value) / whereNotLike($column, $value) whereExists($query|SqlExpression) / whereNotExists($query|SqlExpression) |
'=' => 'eq', '<>' => 'neq', '!=' => 'neq', '<' => 'lt', '<=' => 'lte', '>' => 'gt', '>=' => 'gte', 'in' => 'in', 'between' => 'between', 'like' => 'like', 'exists' => 'exists' |
Сравнение с другим полем
Отдельный метод whereColumn упрощает сравнение полей друг с другом:
\Bitrix\Main\UserTable::query() ->whereColumn('NAME', 'LOGIN') ->exec(); // WHERE `main_user`.`NAME` = `main_user`.`LOGIN` |
\Bitrix\Main\UserTable::query() ->where('NAME', new Query\Filter\Expression\Column('LOGIN')) ->exec(); // WHERE `main_user`.`NAME` = `main_user`.`LOGIN` |
\Bitrix\Main\UserTable::query() ->whereIn('LOGIN', [ new Column('NAME'), new Column('LAST_NAME') ]) ->exec(); // WHERE `main_user`.`LOGIN` IN (`main_user`.`NAME`, `main_user`.`LAST_NAME`) |
Множественные условия
Для нескольких условий предполагается такая запись:
\Bitrix\Main\UserTable::query() ->where('ID', '>', 1) ->where('ACTIVE', true) ->whereNotNull('PERSONAL_BIRTHDAY') ->whereLike('NAME', 'A%') ->exec(); // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%' |
Если хочется указать несколько условий в одном вызове, можно использовать такой формат:
(в том числе, операторные методы можно заменять кодами операторов)
\Bitrix\Main\UserTable::query() ->where([ ['ID', '>', 1], ['ACTIVE', true], ['PERSONAL_BIRTHDAY', '<>', null], ['NAME', 'like', 'A%'] ]) ->exec(); // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%' |
OR и вложенные фильтры
Настало время углубиться в архитектуру. Для хранения всех условий фильтра в Query используется контейнер условий Query\Filter\ConditionTree. Помимо стандартных условий, в него можно добавлять другие экземпляры ConditionTree, таким образом создавая любой уровень ветвления и вложенности.
Все приведенные выше вызовы where - это на самом деле проксирование к базовому контейнеру. Следующие два вызова приведут к совершенно одинаковому результату:
\Bitrix\Main\UserTable::query() ->where([ ['ID', '>', 1], ['ACTIVE', true] ]) ->exec(); \Bitrix\Main\UserTable::query() ->where(Query::filter()->where([ ["ID", '>', 1], ['ACTIVE', true] ]))->exec(); // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' |
\Bitrix\Main\UserTable::query() ->where('ACTIVE', true) ->where(Query::filter() ->logic('or') ->where([ ['ID', 1], ['LOGIN', 'admin'] ]) )->exec(); // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin') |
\Bitrix\Main\UserTable::query() ->where('ACTIVE', true) ->where(Query::filter() ->logic('or') ->where('ID', 1) ->where('LOGIN', 'admin') ) ->exec(); // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin') |
Выражения
В новом фильтре, как и в нынешнем `select`, можно в качестве имен полей задавать ExpressionField, которые будут автоматически зарегистрированы как runtime поля сущности.
\Bitrix\Main\UserTable::query() ->where(new ExpressionField('LNG', 'LENGTH(%s)', 'LAST_NAME'), '>', 10) ->exec(); // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10' |
\Bitrix\Main\UserTable::query() ->where(Query::expr()->length("LAST_NAME"), '>', 10) ->exec(); // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10' \Bitrix\Main\UserTable::query() ->addSelect(Query::expr()->count("ID"), 'CNT') ->exec(); // SELECT COUNT(`main_user`.`ID`) AS `CNT` FROM `b_user` `main_user` |
count / countDistinct / sum / min / avg / max / length / lower / upper / concat |
Совместимость с getList
Если вместо цепочки вызовов Query вы предпочитаете старый добрый getList, то смело вставляйте туда новый фильтр вместо массива:
\Bitrix\Main\UserTable::getList([ 'filter' => ['=ID' => 1] ]); \Bitrix\Main\UserTable::getList([ 'filter' => Query::filter() ->where('ID', 1) ]); // WHERE `main_user`.`ID` = 1 |
Условия JOIN
Было бы логично воспользоваться этим инструментом для описания референсов. Оно приобрело такой формат:
new Entity\ReferenceField('GROUP', GroupTable::class, Join::on('this.GROUP_ID', 'ref.ID') ) |
new Entity\ReferenceField('GROUP', GroupTable::class, Join::on('this.GROUP_ID', 'ref.ID') ->where('ref.TYPE', 'admin') ->whereIn('ref.OPTION', [ new Column('this.OPTION1'), new Column('this.OPTION2'), new Column('this.OPTION3') ] ) |
SELECT *
В селекте появились шаблоны, например
->addSelect('UF_*') |
Просто звездочка работает как и прежде:
->addSelect('*') |
->addSelect('**') |
->addSelect('????') |
Не подпадают под шаблон только ReferenceField, их всегда нужно перечислять явно, чтобы избежать неконтролируемое число джойнов таблиц.
Свой тип поля
Раньше доступные типы данных были ограничены штатным набором полей. Теперь можно создавать свои типы и использовать их не только в описании сущностей, но и при фильтрации. За это отвечают интерфейсы Field\IReadable и Field\IStorable. Первый используется для вычисляемых полей, которые можно только считывать (например, ExpressionField), второй - для полей, которые можно хранить и считывать (все скалярные поля). Вот как это выглядит на примере:
class StringField extends ScalarField implements IStorable { ... public function convertValueToDb($value) { return $this->getConnection()->getSqlHelper()->convertToDbString($value); } } |
UNION
Еще одно не связанное с фильтрацией нововведение - упрощение построения UNION запросов:
\Bitrix\Crm\LeadTable::query() ->addSelect('ID') ->addSelect('DATE_CREATE') ->addOrder('DATE_CLOSED', 'DESC') ->unionAll(ContactTable::query() ->addSelect('ID') ->addSelect('DATE_CREATE') ->addOrder('DATE_MODIFY', 'DESC') ) ->addUnionOrder('DATE_CREATE') ->setUnionLimit(10) ->exec(); // (SELECT `crm_lead`.`ID` AS `ID`, `crm_lead`.`DATE_CREATE` AS `DATE_CREATE` FROM `b_crm_lead` `crm_lead` ORDER BY `crm_lead`.`DATE_CLOSED` DESC) UNION ALL (SELECT `crm_contact`.`ID` AS `ID`, `crm_contact`.`DATE_CREATE` AS `DATE_CREATE` FROM `b_crm_contact` `crm_contact` ORDER BY `crm_contact`.`DATE_MODIFY` DESC) ORDER BY `DATE_CREATE` ASC LIM IT 0, 10 |
Вопрос, а чего не сделали что то вроде
orWhere, тогда можно было бы сократить описательную часть
Потому что такую запись сложно читать. ACTIVE AND (ID OR LOGIN) или ACTIVE OR (ID AND LOGIN)?
Еще раз вчитался в примеры и текущий вариант просто ужасен
На мой взгляд это прекрасный пример ситуаций, которые нельзя допускать. Ну и показательный пример из Laravel, если вдруг он кому-то кажется образцовым:
LOGIC - да, эхо. Но как только появляется запрос сложнее чем два условия по OR, этот подход себя оправдывает.
В итоге получим запрос вида
PS. и собственно еще раз, как сделать "ACTIVE AND LOGIN OR UF_SUPERACCESS"? Я ответа как то не заметил.
Я вижу только такой вариант и он как то ... не очень
Мой ответ - это хороший пример того, как делать нельзя. А нужно делать подфильтр с явным указанием приоритета операторов. Выше есть пример:
И ваш пример тоже подходит по сути. Немного адок, как у доктрин, но читается более легко и имеет понятный сходу вид - как в коде, так и в sql запросе. Мало кому нравится читать колбасу из AND/OR без скобок.
То, что некоторые продукты популярны благодаря низкому порогу вхождения, не значит ведь автоматом, что нам нужно копировать их подходы? При разработке фильтра мы смотрели в первую очередь на Doctrine как единственную серьезную ORM на php, и на Eloquent как простой интерфейс для быстрого старта. С or были некоторые дебаты, но остановились на том, на чем остановились.
Что касается своего пути - если вы подобно к Михаилу цепляетесь к конструкции X AND Y OR Z, то повторюсь, на мой взгляд это отличный пример, как делать не следует. Что-то из серии @$var и прочего дурного тона. Подход orWhere выглядит красиво только для примитивных запросов X OR Y, а в реальной жизни там начнется нечто невообразимое.
Почему в итоге в Bitrix ORM получился некий "Пенетавр"?
Или тема Doctrine поднимаетчя только тогда, когда ее "удобно" поднять?
Пока, со стороны, больше похоже на то, что, просто, "legacy"-стайл победил
Во-вторых, был от меня выше пример
Что думать, увидев впервые такое в коде? Скорее всего, подумается то же самое - ID=1 OR LOGIN=admin. Вот из-за этих противоречий и был выбран компромисс. Не хочется провоцировать разработчиков и позволять например начинающим специалистам писать плохой код.
Если время покажет, что orWhere просто необходим и мы эдакие бунтари со своим путем против устоявшихся традиций - добавим, это не сложно.
А если говорить про старый код, то его по мере необходимости и случая пересаживают на новое ядро. Разом это сделать для всего продукта, увы, не получится.
и мне вот одно не понятно.
если так все круто в Laravel и все там красиво и замечательно - тогда зачем страдать с Bitrix ? почему просто не взять и перейти на CMS October или голый фрймворк Laravel.
но нет, с завидной регулярностью появляются коменты и нытье про то почему Bitrix делает не как у всех.
Отвечу за себя, но применимо для большинства наверное: потому что использовать битрикс заставляет бизнес, из-за маркетинга, крутой админки и т.д. И в Laravel не думаю, что так всё круто и замечательно.
никто не мешает использовать свою загрузку (да да именно, bitrix никак не мешает подключать сторонние автозагрузчики).
оформить свои классы в composer который поддерживает различные стандарты автозагрузки а не только PSR-4.
и подключать в модуле require "vendor/autoload.php"
мы это уже используем в коммерческих решениях для marketplace и некоторые существующие решения из pakagist использующие PSR-4 - так к чему эти пустые претензии тогда?
кто вас ограничивает в жесткой структуре?
придумайте аргументы повесоммее.
Нормальное такое нытье, вполне справедливо ожидать подобное в современных реалиях, почему не учитывать признанные сообществом стандарты? Это к вопросу, к чему такие, якобы, пустые претензии.
или когда используете window - ноете почему он не запускает широкоиспользуемые на всех серверах linux приложения и наоборот?
во вторых bitrix не может за вас соблюдать стандарты кода которые соблюдают современные фреймворки.
потому что ничего не знает про ваш код.
а вы можете соблюдать так как код ваш - значит проблема в вас а не в bitrix.
ну если битрикс никак не мешает использовать ваши любимые широкоиспользуемые стандарты - значит проблема в вас.
- вы видели подход на виджетах Yii - когда верстку превращаем в классы компоненты ?
- Phalcon - писать ядро фрейморка на С в надежде получения ускорения вместо того чтобы перейти на высокопроизводительные технологии, тот еще маразм. даже у крупной компании как facebook не получилось hhvm и hack протащить в массы что уж говорить о каких то поделках типа Phalcon
А что понимаете под трешаком? Что там свой "особый" синтаксис? Но опять-таки это продукт на Laravel, т.е. к самому него у вас нет претензий?
- вы видели подход на виджетах Yii - когда верстку превращаем в классы компоненты ?
Как пример, есть шаблонизатор в виде расширения для PHP - Blitz от Badoo, он, конечно, не стал популярным, но, как говорят создатели, это быстрее, если использовать PHP как шаблонизатор. В целом, это понятно, но опять же, для большинства проектов нужно ли это? Многие ли задумываются над ускорением рендеринга шаблона? Я думаю, большинство это мало волнует, но это не значит что это расширение не имеет никакого смысла. С Phalcon такая же ситуация, если для меня критична производительность, и нужен MVC-фреймворк?
не вижу как тут синтаксис облегчит процесс интеграции дизайна
- если критична производительность - то нужно забыть про фрейморки вообще.
т.е. если вам нужно MVC - значит вам не нужна производительность.
пример - сервис vkontakte
такое уже было - был какойто фрейворк в виде pecl писаный китайцами - все его успешно забыли.
просто не надо выдавать говно-стандарты и решения от каких то индусов за священную корову.
А паттерны и стандарты так вобще для дураков придуманы.
просто не надо выдавать говно-стандарты и решения от каких то индусов за священную корову.
Хочу услышать от адептов, как они кайфуют от js библиотеки BX и кастомизации оформления заказа.
а что BX - хорошая библиотека. позволяет обойтись без jquery. если конечно ваша работа не заключается в подключении плагинов.
если кастомизация нужна клиенту - то он выкладывает неплохую сумма за это.
а так попробуйте кастомизировать magento или whocommerce - там такой же кайф.
bitrix никак не ограничивает в использовании сторонних решений.
bitrix никак не ограничивает в использовании сторонних решений.
нравится обмазываться компонентами Laravel - ну и используйте их. в чем проблема?
Было бы неплохо, если бы это поспособствовало продолжению и переводу модуля инфоблоков на d7
Она же не на всех модулях работает, только каталог?
Уже сторонние решения для выборки свойств есть, довольно давно, кстати -
Если без текущих доработок ОРМ никак нельзя подступиться к инфоблокам - ок, будем надеяться, что вы на правильном пути. Но моё мнение, что уже можно было выдать работающую систему для инфоблоков, а потом уже совершенствовать подходы к фильтрации и прочие, опубликованные здесь, вещи.
И, сорри за офтоп, но не могу молчать - мои пять копеек про новый, некастомизируемый, sale.order.ajax (тут выше обсуждали). Моё стойкое мнение, что подход надо менять. Должны существовать заготовленные html-шаблоны каждого типа выводимых данных (можно лучше проработать совместимость со старым шаблоном). Тогда с этим можно будет удобно работать.
Ну и косяки с отправкой пустых заказов без товара из-за подвисаний соединения у клиента, когда основной заказ уже ушел - это вообще за гранью понимания. Также как отсутствие пересчета скидок в заказе, если не был отправлен отдельный ajax-запрос.
нужно кастомные таблицы - используете D7, изучая работу по исходникам. или сторонние решения партнеров.
нужно стабильно и надежно с хорошей документацией - используете инфоблоки со старым API
вы ультимативно выше заявили что у вас все работает в новом ядре.
я писал
новое ядро - для кастомных таблиц.
старое ядро - для инфоблоков
мысль вроде ясно изложил. какие еще вопросы?
новое ядро - есть агрегированные функции, нет поддержки св-в ИБ
старое ядро - нет агрегированных функций, есть поддержка св-в ИБ
претензии к обоим ядрам вроде ясно изложены
оно только для совместимости. и неважно что в новое ядро добавляют.
- третий раз повторяю вам надо новые фишки D7 именно для ИБ - вы и делайте сами или пользуйтесь сторонними наработками.
мне лично неплохо живется в старом ядре без всяких агрегированых функций.
у вас какойто комплекс в потребности универсального решения. с возрастом и опытом это проходит.
нужно просто осознать и принять реальную действительность.
оно только для совместимости. и неважно что в новое ядро добавляют.
мне лично неплохо живется в старом ядре без всяких агрегированых функций.
Ну а живется без агрегированых функций видимо хорошо, только лишь потому, что создаете, видимо, только проекты на уровне сайтов визиток.
нужно просто осознать и принять реальную действительность.
А так да, проходит, путем использования другого инструмента.
высшие существа давно открыли для себя SQL с его неограниченными возможностями.
что внутри не важно. важно что задача решена и полезна для бизнеса, и реализована с приемлимыми затратами.
Не очень удобно писать такую портянку каждый раз, когда нужна сортировка вида ASC, NULLS.
Таким образом проще на чистом SQL написать запрос, чем вручную генерить все эти куски кода.
Было бы здорово сократить этот код и реализовать доп функцию в ядре(!), которая сама сформирует рантайм поле.
На мой взгляд - на первом месте должна быть удобность использования при написании клиентского кода.
Например, вот так было бы намного сподручнее.
---
bitrix d7 orm sort order asc,nulls
Интересный комментарии, целый холивар получился на счет старого и нового ядра.
[[1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE `b_kontragentypodrazdeleniya` SE T `UF_XML_ID` = '8bbcb863-d2f7-4a35-9a11-' at line 1!]