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

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

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

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

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

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

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

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

Применение некоторых миграций к другой базе данных

В классе миграции переопределить метод init():