Как стать автором
Обновить

Комментарии 67

А чем они вам мешают?

Тем, что сначала выполняется join запрос, а потом in. Зачем выполнять запрос два раза? Неужели никто не сталкивался с ограничением длины запроса? Почему же тогда для каждой ячейки каждой строки не выполнять отдельный запрос?
Я бы попросил аргументировать минусы к комментарию. Возможно, я чего-то не понимаю? Как мне кажется, данная ситуация с запросами упрощает взаимодействие AR моделей, но отрицательно сказывается на производительности. Или наоборот? Объясните, пожалуйста.

На производительности это практически не сказывается. Если бы сказывалось значительно, мы бы себе не позволили применить такое решение.

Пока соглашусь. В любом случае, действительно, в большинстве мест мне проще использовать AR. Для больших запросов все равно нет смысла.
Тем, что сначала выполняется join запрос, а потом in.

В Yii 1.1 мы пытались вытащить связанные модели из одного JOIN запроса. В большинстве случаев это работало, но не всегда. Особенно для запросов с GROUP, HAVING и т.д.


Код при этом был довольно сложный. Из за этого возникали непоправимые баги.


В 2.0 путём переделки запросов на WHERE IN(...) удалось упростить код, обрабатывающий результат в разы и уйти от всех проблем, которые нас приследовали в 1.1.


Это про причины такого поведения в 2.0.


Зачем выполнять запрос два раза?

Затем, что иногда всё-таки надо применить условие по таблице из JOIN.


Неужели никто не сталкивался с ограничением длины запроса?

Нет. ActiveRecord не стоит пользоваться для импорта-экспорта. В нормальных ситуациях в WHERE IN оказывается не более сотни ID-шников. Плюс-минус.


Почему же тогда для каждой ячейки каждой строки не выполнять отдельный запрос?

Потому что нет необходимости? :)

Кстате по поводу WHERE IN, в laravel5 так-же используется такой подход, при этом в Yii2 есть with и joinWith.

Так вот вопрос: WHERE IN используется в основном вместо join для того, чтобы было легче написать код билдера и отойти от некоторых багов при этом производительность примерно одинаковая (что JOIN, что WHERE IN)?

Да. Плюс можно делать аналог JOIN между двумя не связанными хранилищами. Например, MySQL и Redis.

Тогда еще пользуясь случаем спрошу про полиморфические связи. Вроде в Yii2 такой реализации нет, но вот в Laravel5 сделали. Тоесть смысл такой, что есть entity_id и type. Type это алиас, который соответствует определенной таблице, а entity_id соответственно идентификатор сущности. Тогда появляется возможность джоинить разные данные из других таблиц.

Но остается такая задняя мысль, что по скольку нет возможности поставить внешний ключ, мы не можем гарантировать целостность базы и пользоваться фишечками типа каскад, делет и тп. На сколько это вообще оправданный функционал?

Бывает, конечно, что и кстати, но да, без внешний ключей как-то не очень.


На тему сложных отношений есть вот такое: https://github.com/yii2tech/ar-role

А как было бы хуже:
1) Отсутствие ключа и абстрактное entity_id
или
2) Перечисление всех полей для джоина на другие таблицы: student_id, instructor_id, tester_id, но с ключами.

От ситуации зависит.

Понял. Импровизирую. Спасибо.
С with на yii2 тоже не проблема сделать полиморфные, а вот с joinWith проблема
Это далеко не так. Более того, существуют ситуации когда два и более простых запроса выполняются гораздо быстрее одного сложного. Если говорить о Mysql то собака порылась в оптимизаторе запросов, который не всегда может оптимизировать ваш запрос так, чтобы он выполнялся максимально эффективно.

Павел Климов из core team сделал расширение чтобы убирать дополнительные запросы в качестве эксперимента. Вот что у этого расширения в аннотации:


3) Despite extra query removal, this extension may not actually increase overall performance. Regular Yii eager join query is very simple and fast, while this extension consumes extra memory and performs extra calculations. Thus in result performance remain almost the same. In most cases usage of this extension is a tradeoff: it reduces load on Database side, while increases it on PHP side.
вот за это я и не люблю ORM, для сложных запросов всегда пишу sql запросы сам, orm использую только для простых вещей
Иногда очень хочется использовать ORM для many-to-many. В yii 1 удавалось создать максимально эффективный запрос. Во 2 yii уже так просто не выходит.

Для действительно сложных запросов действительно стоит писать SQL. Но AR — штука полезная потому как большинство запросов в любом приложении очень простые.

Вообще как я понял лучше всего завернуть все в репозитории и вообще инкапсулировать все ваши запросы. Тогда разницы на чем вы их пишите не будет. Так как если, что-то просядет, мы быстренько подменим реализацию на другой репозиторий и все должно отработать как следует.
+ Как многие утверждают: за ранее мы не можем узнать, что и где просядет при нагрузках. Тоесть подстава может быть в самом простом запросе счетчика.

Да, так и есть.

Спасибо за Asset-ы
Спасибо. Было бы круто, если в миграциях enum добавили

Закиньте в issue на GitHub, если там этого ещё нет.

Спасибо, свойство $modelClass в ArrayDataProvider в самый раз!
Радуете, ребята.
Вот была одна большая неприятность у сотрудника — он из-за своей невнимательности условие запроса записал в Model::find($condition) вместо Model::find()->where($condition) и получая ActiveQuery думал, что условие запомнилось. Дальше по нему выполнялся метод delete(). И вместо того, что бы удалить несколько записей из БД (по условию) он удалил все записи… а это был продакшн и печаль получилось… Дак вот круто было, что бы из коробки Yii2 он кидать исключение о том, что в find() нет аргументов при их использовании…
С подходом «опа-опа и продакшен» никакие фичи фреймворка не помогут. Поможет внедрение CI, тестирования и код-ревью.
Справедливости ради случай с удалением всех строк из таблицы фреймворки действительно могли бы и обрабатывать. Т.е. при генерировании запроса DELETE FROM table_name из querybuilder-a кидать исключение, а если нужно действительно удалить все строки, то завести для этих целей отдельный метод truncate().
delete from table и truncate table — это несколько разные вещи все же. Лучше отдельный метод deleteAll(), а truncate() пусть делает truncate.
А если прилетит delete с условием которое всегда истина? Что прям все варианты разбирать?
Не помню как в yii, но у меня есть delete у модельки. Тут не напортачишь. А если вызывается метод массового удаления, то надо быть максимально внимательным и да, правильно говорят — не забывать тестировать.
Массовое удаление это такая вещь на которой многие спотыкались. И много думалось о том как лучше сделать.
Лично мое мнение — надо на прикладном уровне в модельке/билдере для удаления делать «корзину», а ядро не трогать, и лишних исключений не делать.
Оптимизация безопасности delete (и update к слову тоже, а то типа никто никогда не затирал важных атрибутов не тем данным) заканчивается когда делают что без условия ничего не меняется/ну удаляется (хотя на find* спокойно ставится условие вроде where 1=1). Всё остальное как правило из области «одной рукой лечим, другой калечим».
>> А если прилетит delete с условием которое всегда истина? Что прям все варианты разбирать?

Нет, просто «Нет WHERE блока — исключительная ситуация».
Ну допустим.
Но это не так просто — взял да и запилил.
Тут нужно продумать, а где бросать исключение?
На низком уровне оно по идее и так не пропустит, скорее всего подставив дефолтное «1=1».
У меня в фреймворке для изменяющих запросов по умолчанию подставляется FALSE, что на низком уровне превращается в «1=2».
И если тут не так, то может и стоит подумать о другом дефолтном поведении на низком уровне.
Кидать исключение на высоком уровне? Разве что при выполнении/компиляции запроса. Но тут метод универсальный, и делить его на изменяющий и неизменяющий — та еще работа.
А на уровне построителя — пока на выполнение/генерацию запроса не послали, так в любой момент могут условие добавить, так что некошер. Но ок. Вставили мы куда-то исключение, всё супер. Даже не перетрудились и ничего не поломали. Результат какой? Перечитайте еще раз сообщение с которого начался этот тред — наши пляски с бубном ему бы не помогли. Ведь он потерял условие у find. А тут обязательное условие — однозначное зло ибо ломает целый вагон кейсов где оно не нужно.
Заглянул в исходники Propel2. Нашел такой блок https://monosnap.com/file/YJxmtXGp7qEiF3k4VXV4rjTKe9bTKC
ModelQuery::create()->delete(); не сделаешь уже. По крайней мере в случае человека выше (https://habrahabr.ru/post/305432/#comment_9696300) защитило бы.
серьезно думаете, что задача фреймворка — «защита от дурака»?
По такой логике шаблонизатор с автоэкранированием — защита от дурака. А что, забыл экранировать на выводе — твои проблемы.

Фреймворки и библиотеки должны по возможности защищать от типичных ошибок. DELETE и UPDATE без WHERE блока в 99% случаев — ошибочная ситуация. Ну на крайняк WARNING кидать неплохо бы.
автоэкранирование — одна из задач шаблонизатора и один из самых главных плюсов.
Автоэкранирование в шаблонизаторе это примерно как магические кавычки для защиты от sql-инъекций:
http://php.net/security.magicquotes

Или register_globals

Раньше все дрочили на это.
Сейчас все проклинают.

Автоэкранирование не панацея от XSS.

Все это нужно для низкоквалифицированных разработчиков.
пруф на проклятия, пожалуйста.
Низкоквалицифированные разработчики как раз проводят аналогии между magic quotes и автоэкранированием в шаблонах.

На самом деле, автоэкранирование в шаблонах по сути своей намного ближе к emulated prepares в pdo.
Это сложный идеологический спор о том где лучше экранировать/фильтровать данные для вывода.
У обеих сторон есть свои за и против. Но это не имеет отношения к теме обсуждения просто потому что хорошее оно или плохое автоэкранирование в шаблонах, но цель его не в защите от дурака, а в упрощении работы программиста.
>автоэкранирование в шаблонах по сути своей намного ближе к emulated prepares в pdo.

С чего бы?
Подготовленные выражения работают явно.
Мы вызываем функционал, который будет экранировать.

Да и htmlspecialchars не экранирует…
htmlspecialchars преобразует специальные символы в HTML-сущности.

«Автоэкранирование» не панацея.
А иногда оно лишнее.
Например нужно вывести строку, где должен быть кусок html, а остальные данные с базы.
Если понядеятся на «автоэкранирование», то html выведется испорченный.
Если отключить «автоэкранирование», то с базы может пролезть html.
Экранировать нужно вручную то, что нужно.

Если используется кеширование, то заэкранировать лучше один раз и сохранить в кеш.
Я шаблоны вообще не кеширую, кеширую только «контроллеры».
Часто в базе хранится уже обработанный html, или про-htmlspecialchars-енный, или про-strip_tags-енный. Повторная обработка испортит его.

А так да, для клепания говносайтов низкоквалифицированными разработчиками автоэкранирование нужно. :)
Часто в базе хранится уже обработанный html, или про-htmlspecialchars-енный, или про-strip_tags-енный. Повторная обработка испортит его.

Это зря.

Зря повторно обрабатывать или зря хранить обработанный? :)

И то и другое.

Экранировать по умочалнию или только по запросу каждый решит для себя. Но проблемы надуманные.

обработанный html — для таких особых случаев в шаблонизаторе есть особые методы, которые позволяют выводить чистый html.
про-htmlspecialchars-енный — а зачем это хранить? Если легаси или он таким приходит, то можно использовать htmlspecialchars_decode
strip_tags — а если тексте есть строка " " (неразрывный пробел)? Без htmlspecialchars выведется просто пробел.
Внимательно посмотрим на эти строки:

1) PDO prepares:
SELECT * FROM users WHERE username = :username
2) Template engine:
Username: {{ username }}

И в том, и в другом случае в результате получается выражение с корректно отформатированным строковым литералом на целевом языке — SQL и HTML соответственно.

Иногда надо и в SQL-запрос подставить SQL-выражение, и в HTML кусок HTML. Это в обоих случаях записывается особым образом.
Выше написал комментарий, что создатели Propel2 таки считают, что «защита от дурака» — задача фреймворка. https://habrahabr.ru/post/305432/#comment_9745876
Так вы расскажите «сотруднику», что существуют IDE, которые умеют предупреждать о неверном количестве параметров для метода и не только.
Почему в find нельзя передавать параметры?
Кстати да, пропустил этот момент. Это еще один аргумент почему не стоит трогать то что работает, когда изменение глобальное а цель несет ничтожную. Сразу живой пример почти в тему $activeRecord->delete() параметров не подразумевает. Но в прикладной задаче я его переопределил, и при отсутствии параметра идет «удаление в корзину», а при $activeRecord->delete(TRUE) вызывает родителя и действительно убивает. Параметра не было, но он появился.
Может это как в ситуации с load в моделях. Там стоит условие is_array, а мне нужно загрузить данные из API, а они object. Я писал в issues по этому поводу, но вразумительного ответа не получил.
Тут скорее пулреквест надо писать.
Таких мест обычно во всех фреймворках десятки где массив и не обязателен, а подойдет и объект с ArrayAccess да править лень, ибо редко когда на что-то влияет.
Или делайте преобразование перед лоад или к issues пулреквест прикладывайте.
Лично я бы на месте мейнтейнеров yii забил бы на такой issues а пулреквест скорее всего принял бы.
Спасибо! А о самом главном (fxp/composer-asset-plugin:^1.2.0) в статье умолчали ;)
У себя стали использовать дополнительный репозиторий asset-packagist.org и все работает быстро и стабильно без дополнительных плагинов.

Да, вариант.

>Улучшили производительность перевода сообщений при использовании базы данных. Добавили нужные индексы.

Позор, что их до сих пор не было.

Ну вот теперь они есть. Позор смыт, честь восстановлена :)

Yii не следует SemVer? Немного странно видеть UPGRADE.md для патчей

Это не патчи. Патчи были бы 2.0.8.1.

А что тогда означают 2.0.9?


Согласно документации:


Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.
Yii всегда плохо соблюдали нумерацию версий, и вроде как писали что и во второй версии они ее не придерживаются.
Вероятно сложно съехать с уже проложенных рельс, уже распланировано что куда, созданы ветки, куча микроскопической, но работы.

То цитата из документации SemVer.
Теперь вижу, что не следуете. Точнее следуете по шаблону 2.MAJOR.MINOR.PATCH.
Только вот в composer разве можно указать "2.0.0.*"? В случае какой-то критической ошибки в безопасности появятся версии 2.0.0.1, 2.0.1.1 и тд? Или она будет устранена в 2.0.10?

Только вот в composer разве можно указать "2.0.0.*"?

Да.


В случае какой-то критической ошибки в безопасности появятся версии 2.0.0.1, 2.0.1.1 и тд?

Да.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории