Многие, кто писал приложение на yii 1.x, (на самом деле как я понимаю все нижеописанное актуально и для yii 2.x) и если проект достаточно сложный,n в какой то момент приходил к ситуации, что модели становились толстые, что, количество сценариев в модели растет, методы beforeSave, beforeValidate, afterValidate становятся неуправляемые и все это превращается в нетестируемый, неуправляемый код. И тут появляется то самое чувство, что ты делаешь что-то не так.
Вариантов решения на самом деле наверное несколько, но я почему-то созрел и увидел только на основе Layered Architecture. Достаточно логично и легко применимый шаблон для приложения в описанной ситуации.
Итак вы в ситуации когда менять и добавлять логику сложно, но бизнес требует. Две важные установки, который приходится следовать:
- Новую логику добавляем используя новую предложенную архитектуру.
- Старую логику, перед изменением оцениваем объем работ по изменению в текущем виде, и если это достаточно затратно по времени, а может быть и нервная система будет страдать, то опять же переписываем уже
- измененную логику под новую архитектуру.
Как она теперь выглядит эта "новая архитектура" ?
- Ваша модель больше не используется для валидации пользовательского ввода. Вся старая валидация остается там, а вот для новой логики, больше не пишем валидацию в модели.
- Больше не добавляете ничего нового в метод
пример: Ваша бизнес логика требует ввод новых данных от пользователя и сохранение этих данных в базу. Пусть это будет просто ModerateComment. Фича, позволяющая вам модерировать комментарий для вашего блога. Все просто, старый метод потребовал бы от нас просто добавленеи нового сценария и вызова метода "save". У модели Comment. И может быть этот простой путь покажется вам проще нижеописанного, но вы понимаете, это просто пример который сильно упрощен и этим путем мы уже написали в своем приложении модель на сотню килобайт и даже такая простая задача требует от нас теперь очень много времени, потому что старую логику приходится перепроверять и не стараться не сломать.
Мы будем использовать теперь несколько новых для нас сущностей. Для примера новая логика будет названа "ModeratePost"
- ModerateModel - для валидации пользовательских данны
- ModerateService - сущность которая будет непосредственно писать и читать в/из базы данных.
Введение двух новых сущностей как раз и позволяет нам изолировать новую логику от старого приложения. В UML это будет выглядеть примерно так:
+---------------+ +-----------------+
| ModerateModel | | ModerateService |
| | | |
+---------------+ +-----------------+
| | | |
| | | |
| | | |
| | | |
| | | |
+---------------+ ++----------------+
Наша модель теперь ответственна только за валидацию пользовательских данных, а Service за действия связанные с базой данных.
Такой подход позволяет изолированно производить тестирование модели, не затрагивая базу данных. Ну а также тестировать код, который что-то пишет или читает из базы с помощью phpunit only или в паре с codeception. Знаете yii фикстуры ? phpunit тоже их умеет.
Ну теперь немного кода:
class ModerateModel extends Model
{
/**
* yii валидация, здесь можно описать правила.
* Для нашего простого примера это только 1 поле is_moderated.
* Пример для yii 1.x
*/
public function rules()
{
return ['is_moderated', 'in', 'range' => [1], 'allowEmpty' => false];
}
}
/**
* Класс для работы с базой данных.
* Не наследуется ни от каких классов фреймворка. Yii умеет только
* ActiveRecord для работы с базой, мы уже наелись проблем этого
* паттерна.
*/
class ModerateService
{
/**
* Ставит колонку is_moderated в таблице комментариев в 1.
*/
public function moderate(Comment $comment)
{
$command = Yii::app()->db->createCommand('UPDATE comments SET
is_moderated=1 WHERE id=:comment_id')->execute([':comment_id' => $comment->id]);
}
}
Теперь нам не достает только соответсвующего экшена в контроллере.
public actionModerate()
{
$comment = $this->loadModel($_GET['id']);
$service = new ModerateService;
$model = new ModerateModel;
$model->setAttributes($_POST['ModerateModel']);
if ($model->validate()) {
$service->moderate($comment);
}
}
Итак что нам дает такой подход: написание новой логики никак не использует старый код, в который уже не хочется лезть из за страха что-то сломать. Новая логика полностью изолирована и легко может быть протестирована. А это немаловажно: иметь тестируемый код. Отсюда вытекает такая хорошая вещь, как возможность целиком выпилить всю логику, ответственную за модерацию, просто удалив 2 файла и один экшен, без страха что-то сломать.
И еще одна особенность, наш ModerateService
не наследуется ни от
какого класса фреймворка, в будущем, когда выйдет новая версия
фреймворка, вам не потребуется переписывать этот класс. А придется
переписать только класс ModerateModel который используется для
валидации. А ведь со временем приложение обрастает логикой и чем
меньше вам придется переписать, чтобы сменить фреймворк тем
лучше. Фреймворко-независимое приложение это уже сейчас становится
важным. Но это тоже целый подход.
Мне кажется этот подход немного сомнительным. Тоесть сама идея отличная, а вот реализация крайне сомнительна.
Я бы предложил пойти другим путем (как поступил в своем проекте).
Давайте рассмотрим пример, что у нас есть задача написать модуль работы с постами (этакий мини блог). В целях экономии времени рассмотрим только frontend часть.
Итак у нас есть следующие задача:
Конечно если мы все это дело постараемся впихнуть в одну модель, то действительно будет ощущение неловкости и повышенный холестерин.
Но мы можем сделать следующее:
Просмотр всех постов
Для этого мы создаем отдельную модель PostSearch и всю логику для отображения, поиска и сортировки выносим в нее.
Форма создания поста
Для этого мы создаем еще одну модель PostForm, которая будет отвечать за создания поста и решать дополнительные условия (валидация, обработка данных, запись в базу и тп).
Не забудем еще про кастомные квери
PostQuery scope модель, будет содержать методы для упрощения выборки, например status(1), выберет все статьи со статусом 1.
Модель Post
Которая содержит общие данные, например список и перевод атрибутов, название таблицы, реляции и методы для выборки.
Еще чуть чуть бест практика
Не делаем обращений к актив рекорду в контроллеры, все методы для выборки инкапсулируем в модель и максимально разбиваем на скопы.