Служебные данные и интерфейс для управления ими

Вряд ли есть сколько-нибудь крупный проект, который не хранит служебные данные в базе данных. Под служебными данными понимаются даже не совсем данные, а предопределенные константы, которым необходимо участвовать в общей работе приложения. Как один из хороших примеров: права и роли. Есть список прав: createPost, createTopic, createCategory, и список ролей, к которым привязаны эти права.

Удобно, если администратор может просмотреть перечень прав через графический интерфейс; но неудобно, если он их может добавить, а система не сможет сопроводить это изменение собственной обработкой без вмешательства программиста. Мы добавили новое «право» (правило) updCategories! Теперь можно назначить его какой-нибудь роли, например, Moderator. Имеется в виду, что теперь модератор может изменять категории (неважно, что это такое и в каком контексте). Однако без нужной проверки при обновлении категорий (user->can(‘updCategories’)) добавленное правило бесполезно.

И вот кто-то же должен написать эти заветные строки: if (user->can(‘updCategories’)) … Конечно, программист. Если не предусмотрена автоматическая привязка «права» к «тому-где-его-применять», то любое изменение служебных данных, к которым у администратора есть доступ (например, переименование), чревато трудно отслеживаемыми поломками. Буквально в любой части программы вбитое «can(createPost)» может отвалиться по милости администратора, который переназвал это право как «postCreation».

Как эта проблема решается?

  1. Программист говорит администратору — ни за что не трогай эти поля. Или вовсе скрывает их \ делает недоступными для редактирования. А потом каждый раз, когда нужно добавить в базу новое разрешение, или любую новую константу, которая будет использована в программе (а это любые справочные таблицы: типов, прав, ролей, прочее), создаётся миграция, добавляется эта константа, в админке на нее можно посмотреть, и всё. В целом, тоже неплохо. Жить можно.
  2. Программист делает CRUD только тех справочных таблиц, для которых ДЕЙСТВИТЕЛЬНО предусмотрена обработка изменившихся состояний. Есть не очень удачный способ делать это посредством конфигураций, которые могут выглядеть как самодельный псевдо-язык, или как чистый серверный язык (место, где рождаются eval’ы). Тогда при добавлении новой справочной информации сам администратор обязан сопроводить поведением добавляемую константу. Это допустимо только если администратор === программист. Он, так сказать, упростил себе жизнь посредством интерфейса, хотя на prod-сервере внедрение нетестированных поведений всё равно очень и очень спорное решение.

Альтернативой ему служит формализация поведений. То есть, ради гибкой работы справочников проводится специальная работа, планирование архитектуры компонентов, которым можно менять поведение динамически. Т.е., продолжая пример с ролями и разрешениями: создаётся конкретный, ограниченный набор действий, которые может проводить какой-либо пользователь с каким-либо компонентом той части системы, которую мы делаем гибкой. Каждый компонент наследуется от базового класса, и реализует свои поведения, опираясь на проверку возможности того или иного действия: каждый компонент сам знает, в каком месте у него происходит Create, в каком Read, в каком — Update и тд. Эти проверки обязательно должны быть сделаны абстрактными в базовом классе и в дальнейшем должны быть реализованы для каждого класса-потомка. Тогда мы можем указывать конкретные компоненты, и задавать конкретные права для пользователей прямо в админке через графический интерфейс! Но это очень сложная работа (даже, скорее, дорогая, потому что для грамотной её реализации нужен хороший специалист).

Как вывод: спотыкаясь о нежелательное поведение из-за пункта 1, не нужно думать, что это очень плохое решение. То решение, которое лучше — довольно затратное, и не всегда необходимо, а ещё рискует сорвать сроки проекта.

Yii2 и виджеты: немного полемики

Насколько прекрасен и прост Yii2 в работе с backend (хотя, так с любым фреймворком: сколько бы ни ругался на Symfony, но 90% работы происходит с PHP, а не с ним), настолько отвратителен в виджетах и рендеринге. Не знаю, есть ли где-нибудь подобный треш, как в Yii2. Сравнивая с тем же Symfony: там разработчики решили остановиться на дефолтном шаблонизаторе twig и не внедрять в стартовые шаблоны никаких примеров чудо-виджетов. Спасибо, ребят!

Виджеты — ужасная штука. «Это самодостаточный блок», говорили они. «MVC», говорили они. Неужели попытки скрыть передачу данных под видом DataProvider во вьюху действительно не дают повода задуматься?

Как только приходится что-то поменять в виджете, начинается боль. Если вы хотите поменять расположение кнопок — должны потратить два часа на поиски и применение информации. Вы достаточно постигли стандартные виджеты типа GridView? Не волнуйтесь! Любой другой виджет, который вы установите, например, FileInput от Kartik — и вуаля, гуглите снова. Гуглите много.

Окей, я не буду голословным, вот вам код:

Серьёзно, он такой и есть. Выглядит интуитивно понятно и очень гибко. Можно поменять язык, можно настроить множественную загрузку файлов, главное что смотрится красиво! Гляньте:

46078f35a64995249bae95a2076351e7

Да, мне тоже нравится. Работа над плагином проделана большая. Но оставьте это плагином. Зачем делать из него такое, от чего мои глаза ( речь идёт про мои впечатления после использования виджета, и они нисколько не объективны, да ) кровоточили разочарованием в принятых решениях. Побуду циником: взамен предложить нечего. Возможно, потому что Yii2 не стоит лезть во frontend. Возможно потому что отделение backend от frontend есть самое логичное, что произошло с вебом. С тем же вебом, где ECMAScript5 до сих пор не определился, как регламентировать асинхронные процессы, и весь Ajax реализован как бы интуитивно, а не по стандарту ECMA (ну и здорово!).

Давайте вернёмся к коду. Вы заметили ‘pluginEvents’? Это элемент массива, значением которого является… JavaScript! Даже jQuery. Впрочем, на использование jQuery нас обрёк сам фреймворк, но ладно. Дело в том, что приходится писать текст JavaScript прямо в строках PHP. То есть, когда в html встречался коннект к БД, бывалые программисты пускают скупую слезу отчаяния, но сами пишут JavaScript в PHP. Почему? Потому что «виджет есть независимый самодостаточный блок». Тогда что можно назвать виджетом? Календарь? Зачем нам календарик, если мы с ним не можем взаимодействовать? Только в том случае, когда нужно выбрать конкретную дату (и только), он нам пригодится из коробки, как есть. Но если при перелистывании месяцев должно что-то меняться во фронте — о-о, да, запасайтесь кофе и зефирками, впереди долгие бессонные ночи.

JavaScript должен уметь общаться с другим JavaScript. Если нет — в дело пойдут глобальные переменные и window.variable = ‘1’. Как только мы захотим привинтить к календарику что-то красивое, то 1) видим что он виджет и плачем 2) не можем спихнуть эту работу на фронтенда, кроме как заставить всё переписать.

Все видели HTML-разметку? Её обычно много, и в глазах рябит. Но вызывает ли сложности изменить разметку? Хм, обычно нет. Это работа лёгкая и скучная.

Но подождите, сейчас вы увидите на редкость грустную разметку.

Часть шаблона, по которому генерится виджет (точнее, сам плагин). Да, здесь yii2 уже ни при чём, но давайте представим, что нам нужно что-то поменять из этого, а оно оформлено и зашито в виде виджета? А если верстальщик сверстает прекрасное новое оформление для FileInput, отдаст вам, чтобы вы внедрили это, что будет? Снова будет грустно. HTML, от которого просто не может быть весело.

И вы попытаетесь использовать библиотеки виджета, чтобы поменять что-то в нём на js, но не получится! Это ведь самодостаточный независимый блок.

Как вывод, я хочу сказать, что современный веб всегда подразумевает интерактивность компонентов интерфейса. Сейчас такие увесистые компоненты, что перезагружать каждый раз страницу ради серверного рендеринга есть непозволительная роскошь. И если вы загрузили файл, а в списке файлов он отобразится только после ф5, это уже плохо!

Интерактивность требует доступности всех компонентов. В виджетах эта доступность страдает. Может, я не умею их готовить (скорее всего, так и есть) — но зачем учиться готовить то, в чём другим людям придётся ещё долго после меня разбираться? Это одинаково непонятная магия для любого новичка, и одинаково муторная проблема для мидла — виджеты, которые нужно менять, с которыми нужно взаимодействовать, и уж тем более с которыми нужно уживаться, когда пишешь SPA.

Профилирование кода в Symfony

Есть такая замечательная фишка Stopwatch. По сравнению со многими компонентами Симфони, которые нужно доставать через контейнер, инициализировать в каких-то конфигах и прочем, этот компонент самодостаточный и используется простым удобным способом: new Stopwatch. Волшебно! Хоть что-то приятное.

Как сообщается в первоисточнике, есть два способа установки этой штуки. Первый возможен и без самого Симфони:

Второй — использовать официальный репозиторий. Stopwatch есть в ядре фреймворка, можно оттуда её стащить.

Использование

 

Статистика выполнения кода, находящегося между началом и концом секции, попадёт в event, возвращаемый методом stop. Между прочим, start тоже возвращает этот эвент.

Можно вызывать start с двумя параметрами:

В таком случае, у эвента будет собственная категория. Можно объединять эвенты в категории по какому-нибудь признаку. Зачем? Разработчики приводят для примера собственное решение: Symfony Profiler (один из инструментов отладки фреймворка). В нём разные категории эвентов нужны чтобы по-разному их подсвечивать, когда отображается статитстика (например, все эвенты, профилирующие обращения к БД).

Перед тем, как рассмотреть уже наконец понятие «эвент», посмотрим ещё на одну фишку: периоды. Всё те же разработчики объясняют их на примере секундомера: кроме «вкл» и «выкл», секундомер может замерять время конкретного периода, не переставая считать секунды. Это нужно, чтобы подсчитать, какой круг за сколько времени пробежали бегуны, к примеру. Здесь то же самое:

Что из себя представляет эвент? Это объект класса StopwatchEvent с такими функциями:

После работы профайлера используйте эвент, полученный из stop, чтобы собрать всю статистику выполненного кода.

Напоследок: секции.

 

Зачем? Чтобы дозаписывать в секцию какие-либо эвенты и суммировать раздобытую информацию из разных эвентов для упрощения сбора статистики.

Спасибо, Symfony!

Роуты (routes) в Symfony

После Yii2 они кажутся чем-то странным в Symfony, но аннотации, если так присмотреться, очень удобные. Каждый контроллер может быть гибко настроен под любой роут без танцев с бубном в UrlManager (пожалуй, самый яркий недостаток в Yii2, но я всё равно люблю его больше).

как это выглядит?

 

3cd1fe6ffc635d3aec44fce25cc80913Вот так. Аннотация @Route(«/debug/grab») позволяет привести через запрос ‘http://site.com/debug/grab’ именно к этому экшену.

Если нужен динамический url, запросто делаем вот так:

Таким образом удобно передавать в экшен какой-нибудь id и сразу отдавать его в модель. Роуты могут быть самыми разными, могут вообще не соответствовать названию контроллера, экшена и тд.

Чтобы работало описание роутов через аннотацию, очень важно:

Если работаете с PhpStorm, скачайте плагин Symfony, он помогает автоматически выявлять аннотации и делает много полезных вещей при работе с этим фреймворком.

Как выглядит обычный роутинг с помощью настроек routing.yml?

Как-то так. На самом деле, неудобно, но нельзя забивать на этот способ: иногда нам нужны роуты только в dev окружении, а иногда настройка конкретного роута слишком сложна, чтобы пихать её в аннотацию (да-да, в Yii2 для этого использовались всякие verbFilter и прочее). Вот тогда может пригодиться роутинг в конфиге.

Но всё-таки многие потребности роутинга удовлетворяет и аннотация:

То есть, можно указывать и метод передачи, и динамические роуты, и присваивать имена. Кстати, очень полезная штука. Я поначалу не понимал, зачем в конфиге определять роут через имя, как _errors в примере выше. А вот зачем: потом можно заставить Symfony генерировать ссылки на конкретный роут вне зависимости от того, насколько он усложнился или изменился — достаточно только указать имя этого роута.

Пример прямо из шаблона Twig:

Отсылка к предыдущему роуту.

Спасибо этому парню за материал.

Больше инфы.

 

 

Трио методов для асинхронных процессов

Постановка задачи следующая: дать возможность запускать некий асинхронный процесс на сервере, проверять его состояние и получать результат.

Хороший пример: запуск поиска со множеством параметров по множеству API c последующим разбором результатов через адаптеры. Сам процесс нас не волнует, лишь рычаги управления: допустим, метод run или search (что-то вроде ‘http://site.com/api/v1/run’ ) принимает через POST множество параметров для запуска, а возвращает id.

051091cbc58b2d3c549adbd0168cb3db

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

Хорошая идея (и, возможно, первая, которая приходит в голову) — сделать ещё два метода, например, check (или status, я просто привык именовать методы глаголами, для REST это неприемлемо) — ‘http://site.com/api/v1/check/id‘. И get-results — ‘http://site.com/api/v1/get-results/id‘.

Зачем нам нужен check? Чтобы проверять, в каком состоянии находится запущенный процесс с полученным из метода run id. А get-results чтобы получать результаты.

Можно решить эту задачу двумя методами: и правда, зачем вообще нужен check, если у нас есть метод get-results, который просто не вернёт информацию, если её нет. Его можно нагрузить дополнительной логикой и возвращать status как один из ключей ответа. Преимущество такого метода интуитивно понятно:

bef78d760a87e6aab392cff755a8ff0d

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

Но это не панацея для всех проблем такого вида: если процесс поиска составной, и в фоне запускается несколько потоков для выгрузки разнородной информации, то метод get-results может вернуть лишь часть результатов. К примеру, поиск по транспортным средствам, где самолёты и автомобили доступны с разных API — тогда самолёты могут найтись, а автомобили нет. Получится такая ситуация, что мы лишний раз нагружаем сервер, вытаскиваем полученные результаты, а они оказываются слишком громоздкими или неполными, и нам придётся делать ещё один запрос, чтобы получить более полную информацию. Само собой, можно спроектировать метод get-results так, чтобы он сводил потери такого вида к минимуму, но гораздо проще в сопровождении будет отделение статистики и статуса процесса поиска в отдельный метод: check. Он может возвращать статистику: сколько уже найдено, минимальная и максимальная цена \ вес \ что-либо ещё из нужных параметров, при этом не станет затрагивать сами данные.

Валидаторы

унаследовать от yii/validators/Validator;

 

 

Сценарии в Yii2

При определенных в rules правилах валидации можно указывать сценарии чере scenario:

Использовать так:

Остальные поля исключаются из проверки словно их вообще нет.

События в Yii2

 

Сервис-локаторы в Yii2

Чтобы добавить сервис-локатор в проект, нужно написать во входном скрипте index.php

 

далее идет объявление приложения:

$application тоже является сервис-локатором, поэтому можно так:

Как использовать?

Где-нибудь в контроллере:

Либо:

Еще один способ добавить компонент — через config (main) в массиве components;

‘имя_класса’ => [‘class’ =>’путь к классу’]

вызывать так: \Yii::$app->Имя_класса

Что такое компонент в Yii2

Компонент или сервис — такой класс, который может пригодиться в любой части программы.
Компоненты === сервисы.

Хелперы < Компоненты; Хелперы є Компоненты ( Хелперы это компоненты, но не все компоненты — хелперы ).

Модули < Компоненты; Модули є Компоненты. В чём прикол использования компонентов через объявление их в конфигурации приложения? Прикол в том, что такие компоненты регистрируются, и сервис-локатор беспокоится о том, чтобы эти классы были синглтонами. Это экономнее, безопаснее и лучше.

Регистрация компонентов осуществляется через config:

Доступ к зарегистрированным компонентам осуществляется через переменную приложения:

\Yii::$app->ИмяТвоегоКрутогоКомпонента

Если ты поменял расположение компонента, то регистрация компонента послужит дополнительной обёрткой и тебе не нужно будет бегать, заменяя все пути, которые указывали на компонент: достаточно поменять только в конфиге.

Не используй компоненты через use, используй через components => [] в конфиге.

Никогда не верь подобным выводам: просто используй use с умом.

PS инфа из документации:

Информация: Хотя вы можете зарегистрировать столько компонентов в приложении сколько вам нужно, все таки стоит это делать разумно. Компоненты приложения похожи на глобальные переменные. Использование слишком большого количества компонентов приложения может потенциально сделать ваш код сложным для разработки и тестирования. В большинстве случаев вы можете просто создать локальный компонент и использовать его при необходимости.