Многие, кто писал приложение на 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 который используется для
валидации. А ведь со временем приложение обрастает логикой и чем
меньше вам придется переписать, чтобы сменить фреймворк тем
лучше. Фреймворко-независимое приложение это уже сейчас становится
важным. Но это тоже целый подход.
@nepster-web, я так понимаю что в примере @yupe
PostForm
не наследывается отPost
, а отModel
Т.е. это для валидации и указания полей которые модель Post не может иметь в ее таблице, например, для сохранения связанных данных. И вся ответственность за сохранение модели и всех необходимых связей переходит к Repository, что должно решать проблему указанную в посте: