Вышла версия 2.0.9 PHP-фреймворка Yii. Минорный релиз содержит около 60 небольших улучшений и исправлений. Инструкции по установке можно найти на официальном сайте.
В данной версии есть два изменения, которые, хоть это и маловероятно, могут затронуть ваши приложения. Ознакомьтесь с UPGRADE.md.
Спасибо сообществу Yii за пулл-реквесты и обсуждения.
Этот релиз вышел благодаря вам!
За разработкой фреймворка можно наблюдать на GitHub. Также у нас есть Twitter
и Facebook.
Далее мы рассмотрим самые интересные улучшения подробней. Полный список изменений и исправлений можно найти в CHANGELOG.
Фильтр action
\yii\base\ActionFilter
теперь поддерживает маски для only
и except
, что полезно когда
фильтр навешивается на модуль или приложение целиком:
return [
'as filter' => [
'class' => 'app\filters\SomeFilter',
'only' => [
'particular/*', // все действия контроллера 'particular'
'*/captcha', // все действия 'captcha' всех контроллеров
],
],
// ...
];
Улучшения производительности
- Улучшили производительность перевода сообщений при использовании базы данных. Добавили нужные индексы.
- Схема Oracle теперь считывается быстрее.
Построитель схемы и миграции
Был улучшен построитель схемы, который используется в миграциях. Добавили новый метод null()
чтобы указывать возможность записи null
явно. Метод применяется автоматически, если значение по умолчанию — null
.
$type = $this->string(42)->null();
Также добавили метод для своего SQL:
$type = $this->string(15)->notNull()->append('collate ascii_bin')->append('character set ascii');
Синтаксис команды для генерации миграций был немного изменён: _table
и _column
теперь обязательны:
./yii migrate/create create_user_table
./yii migrate/create add_name_column_to_user_table
Провайдеры данных и виджеты
Все улучшения в данном релизе касаются заголовков. В \yii\data\ArrayDataProvider
добавили свойство $modelClass
, через которое можно указать модель для получения заголовков полей. В дополнение \yii\grid\DataColumn
, который определяет поведение для всех столбцов с данными, теперь пытается получить заголовки из filterModel
грида.
Рефакторинг
Из интерфейса ManagerInterface
RBAC выделили CheckAccessInterface
, который может быть полезен при реализации своей проверки доступа.
\yii\web\User::loginByCookie()
отрефакторен для большей расширяемости.
Asset-ы
При перечислении файлов в пакетах asset-ов теперь можно задать путь в null
. В этом случае файлы не регистрируются. Это полезно, например, для регистрации дополнительных файлов для рабочего окружения:
namespace common\assets;
use yii\web\AssetBundle;
class ReactAsset extends AssetBundle
{
public $sourcePath = null;
public $js = [
YII_ENV_DEV ? "//fb.me/react-15.0.1.js" : "//fb.me/react-15.0.1.min.js",
YII_ENV_DEV ? "//fb.me/react-dom-15.0.1.js" : "//fb.me/react-dom-15.0.1.min.js",
YII_ENV_DEV ? "//cdnjs.cloudflare.com/ajax/libs/babel-core/5.6.15/browser.js" : null,
];
}
Логирование
\yii\log\Target::$logVars
теперь можно настроить более тонко:
_SESSION
— пишем глобальную переменную сессии. Всё как и было._SESSION.id
— пишем толькоid
из сессии.!_SESSION.secret
— не пишем ключsecret
из сессии.
Логика такой фильтрации вынесена в \yii\helpers\ArrayHelper::filter()
. При необходимости можно использовать у себя.
Markdown
Тип синтаксиса по умолчанию для yii\helpers\Markdown
теперь можно задать через $defaultFlavor
.
Комментарии (65)
littlefuntik
12.07.2016 00:51-2Радуете, ребята.
Вот была одна большая неприятность у сотрудника — он из-за своей невнимательности условие запроса записал в Model::find($condition) вместо Model::find()->where($condition) и получая ActiveQuery думал, что условие запомнилось. Дальше по нему выполнялся метод delete(). И вместо того, что бы удалить несколько записей из БД (по условию) он удалил все записи… а это был продакшн и печаль получилось… Дак вот круто было, что бы из коробки Yii2 он кидать исключение о том, что в find() нет аргументов при их использовании…symbix
12.07.2016 06:49+4С подходом «опа-опа и продакшен» никакие фичи фреймворка не помогут. Поможет внедрение CI, тестирования и код-ревью.
Blumfontein
12.07.2016 07:07Справедливости ради случай с удалением всех строк из таблицы фреймворки действительно могли бы и обрабатывать. Т.е. при генерировании запроса DELETE FROM table_name из querybuilder-a кидать исключение, а если нужно действительно удалить все строки, то завести для этих целей отдельный метод truncate().
symbix
12.07.2016 07:55+2delete from table и truncate table — это несколько разные вещи все же. Лучше отдельный метод deleteAll(), а truncate() пусть делает truncate.
Mendel
12.07.2016 10:30+1А если прилетит delete с условием которое всегда истина? Что прям все варианты разбирать?
Не помню как в yii, но у меня есть delete у модельки. Тут не напортачишь. А если вызывается метод массового удаления, то надо быть максимально внимательным и да, правильно говорят — не забывать тестировать.
Массовое удаление это такая вещь на которой многие спотыкались. И много думалось о том как лучше сделать.
Лично мое мнение — надо на прикладном уровне в модельке/билдере для удаления делать «корзину», а ядро не трогать, и лишних исключений не делать.
Оптимизация безопасности delete (и update к слову тоже, а то типа никто никогда не затирал важных атрибутов не тем данным) заканчивается когда делают что без условия ничего не меняется/ну удаляется (хотя на find* спокойно ставится условие вроде where 1=1). Всё остальное как правило из области «одной рукой лечим, другой калечим».Blumfontein
12.07.2016 14:11>> А если прилетит delete с условием которое всегда истина? Что прям все варианты разбирать?
Нет, просто «Нет WHERE блока — исключительная ситуация».Mendel
12.07.2016 15:04Ну допустим.
Но это не так просто — взял да и запилил.
Тут нужно продумать, а где бросать исключение?
На низком уровне оно по идее и так не пропустит, скорее всего подставив дефолтное «1=1».
У меня в фреймворке для изменяющих запросов по умолчанию подставляется FALSE, что на низком уровне превращается в «1=2».
И если тут не так, то может и стоит подумать о другом дефолтном поведении на низком уровне.
Кидать исключение на высоком уровне? Разве что при выполнении/компиляции запроса. Но тут метод универсальный, и делить его на изменяющий и неизменяющий — та еще работа.
А на уровне построителя — пока на выполнение/генерацию запроса не послали, так в любой момент могут условие добавить, так что некошер. Но ок. Вставили мы куда-то исключение, всё супер. Даже не перетрудились и ничего не поломали. Результат какой? Перечитайте еще раз сообщение с которого начался этот тред — наши пляски с бубном ему бы не помогли. Ведь он потерял условие у find. А тут обязательное условие — однозначное зло ибо ломает целый вагон кейсов где оно не нужно.
zelenin
12.07.2016 12:08+1серьезно думаете, что задача фреймворка — «защита от дурака»?
Blumfontein
12.07.2016 14:06По такой логике шаблонизатор с автоэкранированием — защита от дурака. А что, забыл экранировать на выводе — твои проблемы.
Фреймворки и библиотеки должны по возможности защищать от типичных ошибок. DELETE и UPDATE без WHERE блока в 99% случаев — ошибочная ситуация. Ну на крайняк WARNING кидать неплохо бы.zelenin
12.07.2016 14:18+1автоэкранирование — одна из задач шаблонизатора и один из самых главных плюсов.
M-A-XG
12.07.2016 20:04-1Автоэкранирование в шаблонизаторе это примерно как магические кавычки для защиты от sql-инъекций:
http://php.net/security.magicquotes
Или register_globals
Раньше все дрочили на это.
Сейчас все проклинают.
Автоэкранирование не панацея от XSS.
Все это нужно для низкоквалифицированных разработчиков.symbix
13.07.2016 08:47+1Низкоквалицифированные разработчики как раз проводят аналогии между magic quotes и автоэкранированием в шаблонах.
На самом деле, автоэкранирование в шаблонах по сути своей намного ближе к emulated prepares в pdo.Mendel
13.07.2016 10:42Это сложный идеологический спор о том где лучше экранировать/фильтровать данные для вывода.
У обеих сторон есть свои за и против. Но это не имеет отношения к теме обсуждения просто потому что хорошее оно или плохое автоэкранирование в шаблонах, но цель его не в защите от дурака, а в упрощении работы программиста.
M-A-XG
13.07.2016 12:42>автоэкранирование в шаблонах по сути своей намного ближе к emulated prepares в pdo.
С чего бы?
Подготовленные выражения работают явно.
Мы вызываем функционал, который будет экранировать.
Да и htmlspecialchars не экранирует…
htmlspecialchars преобразует специальные символы в HTML-сущности.
«Автоэкранирование» не панацея.
А иногда оно лишнее.
Например нужно вывести строку, где должен быть кусок html, а остальные данные с базы.
Если понядеятся на «автоэкранирование», то html выведется испорченный.
Если отключить «автоэкранирование», то с базы может пролезть html.
Экранировать нужно вручную то, что нужно.
Если используется кеширование, то заэкранировать лучше один раз и сохранить в кеш.
Я шаблоны вообще не кеширую, кеширую только «контроллеры».
Часто в базе хранится уже обработанный html, или про-htmlspecialchars-енный, или про-strip_tags-енный. Повторная обработка испортит его.
А так да, для клепания говносайтов низкоквалифицированными разработчиками автоэкранирование нужно. :)t_kanstantsin
13.07.2016 16:45Экранировать по умочалнию или только по запросу каждый решит для себя. Но проблемы надуманные.
обработанный html — для таких особых случаев в шаблонизаторе есть особые методы, которые позволяют выводить чистый html.
про-htmlspecialchars-енный — а зачем это хранить? Если легаси или он таким приходит, то можно использовать htmlspecialchars_decode
strip_tags — а если тексте есть строка " " (неразрывный пробел)? Без htmlspecialchars выведется просто пробел.
symbix
13.07.2016 18:22+2Внимательно посмотрим на эти строки:
1) PDO prepares:
SELECT * FROM users WHERE username = :username
2) Template engine:
Username: {{ username }}
И в том, и в другом случае в результате получается выражение с корректно отформатированным строковым литералом на целевом языке — SQL и HTML соответственно.
Иногда надо и в SQL-запрос подставить SQL-выражение, и в HTML кусок HTML. Это в обоих случаях записывается особым образом.
p0vidl0
12.07.2016 08:01+1Так вы расскажите «сотруднику», что существуют IDE, которые умеют предупреждать о неверном количестве параметров для метода и не только.
springimport
12.07.2016 17:22Почему в find нельзя передавать параметры?
Mendel
12.07.2016 17:54Кстати да, пропустил этот момент. Это еще один аргумент почему не стоит трогать то что работает, когда изменение глобальное а цель несет ничтожную. Сразу живой пример почти в тему $activeRecord->delete() параметров не подразумевает. Но в прикладной задаче я его переопределил, и при отсутствии параметра идет «удаление в корзину», а при $activeRecord->delete(TRUE) вызывает родителя и действительно убивает. Параметра не было, но он появился.
springimport
12.07.2016 18:11Может это как в ситуации с load в моделях. Там стоит условие is_array, а мне нужно загрузить данные из API, а они object. Я писал в issues по этому поводу, но вразумительного ответа не получил.
Mendel
12.07.2016 19:41Тут скорее пулреквест надо писать.
Таких мест обычно во всех фреймворках десятки где массив и не обязателен, а подойдет и объект с ArrayAccess да править лень, ибо редко когда на что-то влияет.
Или делайте преобразование перед лоад или к issues пулреквест прикладывайте.
Лично я бы на месте мейнтейнеров yii забил бы на такой issues а пулреквест скорее всего принял бы.
mamontovdmitriy
12.07.2016 11:09+1Спасибо! А о самом главном (fxp/composer-asset-plugin:^1.2.0) в статье умолчали ;)
SamDark
12.07.2016 13:54Да, об этом действительно забыли, если вы про возросшую производительность и возможность её нарастить ещё :)
Или вы про то, что надо
composer global update
сделать?
BoShurik
12.07.2016 19:03Yii не следует SemVer? Немного странно видеть UPGRADE.md для патчей
SamDark
13.07.2016 02:13Это не патчи. Патчи были бы 2.0.8.1.
BoShurik
13.07.2016 09:23А что тогда означают 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.Mendel
13.07.2016 10:48Yii всегда плохо соблюдали нумерацию версий, и вроде как писали что и во второй версии они ее не придерживаются.
Вероятно сложно съехать с уже проложенных рельс, уже распланировано что куда, созданы ветки, куча микроскопической, но работы.
SamDark
13.07.2016 11:18Это где такое осталось? https://github.com/yiisoft/yii2/blob/master/docs/internals/versions.md
BoShurik
13.07.2016 11:45То цитата из документации SemVer.
Теперь вижу, что не следуете. Точнее следуете по шаблону 2.MAJOR.MINOR.PATCH.
Только вот в composer разве можно указать "2.0.0.*"? В случае какой-то критической ошибки в безопасности появятся версии 2.0.0.1, 2.0.1.1 и тд? Или она будет устранена в 2.0.10?SamDark
13.07.2016 11:58Только вот в composer разве можно указать "2.0.0.*"?
Да.
В случае какой-то критической ошибки в безопасности появятся версии 2.0.0.1, 2.0.1.1 и тд?
Да.
ilyaplot
Когда уже Yii перестанет генерировать длиннющие дублирующие where in запросы для join?
SamDark
А чем они вам мешают?
ilyaplot
Тем, что сначала выполняется join запрос, а потом in. Зачем выполнять запрос два раза? Неужели никто не сталкивался с ограничением длины запроса? Почему же тогда для каждой ячейки каждой строки не выполнять отдельный запрос?
ilyaplot
Я бы попросил аргументировать минусы к комментарию. Возможно, я чего-то не понимаю? Как мне кажется, данная ситуация с запросами упрощает взаимодействие AR моделей, но отрицательно сказывается на производительности. Или наоборот? Объясните, пожалуйста.
SamDark
На производительности это практически не сказывается. Если бы сказывалось значительно, мы бы себе не позволили применить такое решение.
ilyaplot
Пока соглашусь. В любом случае, действительно, в большинстве мест мне проще использовать AR. Для больших запросов все равно нет смысла.
SamDark
В Yii 1.1 мы пытались вытащить связанные модели из одного
JOIN
запроса. В большинстве случаев это работало, но не всегда. Особенно для запросов сGROUP
,HAVING
и т.д.Код при этом был довольно сложный. Из за этого возникали непоправимые баги.
В 2.0 путём переделки запросов на
WHERE IN(...)
удалось упростить код, обрабатывающий результат в разы и уйти от всех проблем, которые нас приследовали в 1.1.Это про причины такого поведения в 2.0.
Затем, что иногда всё-таки надо применить условие по таблице из JOIN.
Нет. ActiveRecord не стоит пользоваться для импорта-экспорта. В нормальных ситуациях в
WHERE IN
оказывается не более сотни ID-шников. Плюс-минус.Потому что нет необходимости? :)
nepster09
Кстате по поводу WHERE IN, в laravel5 так-же используется такой подход, при этом в Yii2 есть with и joinWith.
Так вот вопрос: WHERE IN используется в основном вместо join для того, чтобы было легче написать код билдера и отойти от некоторых багов при этом производительность примерно одинаковая (что JOIN, что WHERE IN)?
SamDark
Да. Плюс можно делать аналог JOIN между двумя не связанными хранилищами. Например, MySQL и Redis.
nepster09
Тогда еще пользуясь случаем спрошу про полиморфические связи. Вроде в Yii2 такой реализации нет, но вот в Laravel5 сделали. Тоесть смысл такой, что есть entity_id и type. Type это алиас, который соответствует определенной таблице, а entity_id соответственно идентификатор сущности. Тогда появляется возможность джоинить разные данные из других таблиц.
Но остается такая задняя мысль, что по скольку нет возможности поставить внешний ключ, мы не можем гарантировать целостность базы и пользоваться фишечками типа каскад, делет и тп. На сколько это вообще оправданный функционал?
SamDark
Бывает, конечно, что и кстати, но да, без внешний ключей как-то не очень.
На тему сложных отношений есть вот такое: https://github.com/yii2tech/ar-role
nepster09
А как было бы хуже:
1) Отсутствие ключа и абстрактное entity_id
или
2) Перечисление всех полей для джоина на другие таблицы: student_id, instructor_id, tester_id, но с ключами.
SamDark
От ситуации зависит.
nepster09
Понял. Импровизирую. Спасибо.
nemesis1980
С with на yii2 тоже не проблема сделать полиморфные, а вот с joinWith проблема
demimurych
Это далеко не так. Более того, существуют ситуации когда два и более простых запроса выполняются гораздо быстрее одного сложного. Если говорить о Mysql то собака порылась в оптимизаторе запросов, который не всегда может оптимизировать ваш запрос так, чтобы он выполнялся максимально эффективно.
ilyaplot
Да и далеко не только мне они мешают. https://github.com/yiisoft/yii2/issues/4631 https://github.com/yiisoft/yii2/issues/3929 — тут даже знакомые лица фигурируют :)
SamDark
Павел Климов из core team сделал расширение чтобы убирать дополнительные запросы в качестве эксперимента. Вот что у этого расширения в аннотации:
adamas_antares
вот за это я и не люблю ORM, для сложных запросов всегда пишу sql запросы сам, orm использую только для простых вещей
ilyaplot
Иногда очень хочется использовать ORM для many-to-many. В yii 1 удавалось создать максимально эффективный запрос. Во 2 yii уже так просто не выходит.
SamDark
Для действительно сложных запросов действительно стоит писать SQL. Но AR — штука полезная потому как большинство запросов в любом приложении очень простые.
nepster09
Вообще как я понял лучше всего завернуть все в репозитории и вообще инкапсулировать все ваши запросы. Тогда разницы на чем вы их пишите не будет. Так как если, что-то просядет, мы быстренько подменим реализацию на другой репозиторий и все должно отработать как следует.
+ Как многие утверждают: за ранее мы не можем узнать, что и где просядет при нагрузках. Тоесть подстава может быть в самом простом запросе счетчика.
SamDark
Да, так и есть.