Тонкий контроллер, всё в моделях

Vano

Новичок
Хочу научится писать красивые модели, типу как SerchModel который генерирует Gii. Чтобы контроллер стал тонким, ато бывают ситуации, когда гет данные для поиска имеют слишком много условий, чтобы перевести их в запрос. К примеру $_GET['count'] = 6; в запросе должно значить >= 6; не хочу делать запрос если count не [1,2,3,4,5,6]. В общем много различных условий. Если запрос создавать в экшне контроллера то получится много букаф, а ведь модели предназначены какраз для того, чтобы представлять данные из БД и их поведения как одно целое.

Где бы почитать об этом, чтобы я смог понять, как это делать. Пробывал пройтись по сгенерированным Гии коду, но меня сбивает с толку DataActiveProvider( о котором я знаю ровным счетом ничего ). Подтолкните плз в правильном направлении.
 

Vano

Новичок
Чуть конкретнее распишу, что хочется примерно видеть:
В контроллере
Код:
$arr = [
    'count' => Yii::$app->request->get('count'),
    'type' => Yii::$app->request->get('type'),
    'type2' => Yii::$app->request->get('type2'),
];
$models = SearchModel::search($arr);  // $models будет массив ActiveRecords, либо пустой, если мне не нравятся гетДанные(типа наврем что выборка ничего не нашла)
В моделе примерно так:
Код:
class SearchModel extends \yii\base\Model
{
    public $count;
    public $type;
    public $type2;

    public function rules()
    {
        return [
            [['count', 'type', 'type2'], 'required'],
            ['count', 'integer', 'integerOnly' => true, 'min' => 1, 'max' => 6],
            ['type', 'integer', 'integerOnly' => true],
            ['type2', 'integer', 'integerOnly' => true],
        ];
    }

    public static function search($arr)
    {
        $searchModel = new self;
        $searchModel->setAttributes($array);
   
        if(!$searchModel->validate()) {
            return [];
        }

        $query = NewsModel::find()
            ->where([
                'type' => $searchModel->type,
                'type2' => $searchModel->type2,
            ]);
        if($searchModel->count == 6) {
            $query->andWhere('count', 6, '>=')
        } else {
            $query->andWhere('count = 6');
        }

        return $query->all();
    }
}
Тут сразу становится понятным, что можна было использовать и rules с NewsModel(как минимум). Вот и вопрос. Как это реализовать правильно, красиво? Подскажите плз статьи/гайды/код, чтобы научиться делать это правильно.
 

stalxed

Новичок
Лично я считаю, что подобную операцию должен делать Service Layer (по Фаулеру).
У Эрика Эванса это называется Application Layer.

Почитать можно книги этих авторов...

Кстати, по Фаулеру Active Record это Row Data Gateway к которому прибавили бизнес логику. В принципе, код выше это оно и есть. Так что имхо, нормальный пример, если проект простой.
 
  • Like
Реакции: Vano

Vano

Новичок
Кто еще что посоветует? Пшпята, накидайте кода на стенку)0)
 

Vano

Новичок
Следущее, что нашел. Допустим есть таблица "Пользователей" (логин пароль и специальность(программист, физик, повар). Специальность берется с таблицы "Специальности". Так же, есть две модели АктивРекорд с описанием этих таблиц. И вот к примеру значит при регистрации пользователь выбирает свою специалность с ActiveForm::field()->dropDownList(). В контроллере получается :
Код:
public function actionSignup()
    {
        $model = new User();
        $professions = Profession::find('id', 'name')->asArray()->all();
        ...
        ...
        return $this->render('index', [
            'model' => $model,
            'professions' => $professions,
        ]);
    }
Ну и во вьюшке :
Код:
...
$form::field($model, 'login')->inputText();
$form::field($model, 'profession')->dropDownList($professions);
...
Правильно ли я думаю, что: Нужно создать модель SignUp, в ней создать метод :
Код:
public function getProfessions()
{
    return Profession::find('id, name')->asArray()->all();
}
валидацию :
Код:
public function rules()
{
    return [
        ...
        ['profession', 'in', 'range', ArrayHelper::getColumn($this->professions, 'id')],
        ...
    ];
}
Тогда контроллер будет тоньше :
Код:
... $model = new Signup(); ...
и вьюшка такая :
Код:
...
$form->field($model, 'login')->inputText();
$form->field($model, 'profession')->dropDownList($model->professions);
...
Балин, пока написал понял, что да круто, так и надо. Тогда вопрос другой: Это и есть примерно правильная реализация тонкого контроллера? Всмысле "когда ты стараешся использовать MVC правильно!, одна из задач с которой ты можеш столкнутся, это пример выше". Я вот просто пытаюсь понять что такое модели в MVC.
 
Последнее редактирование:

Absinthe

жожо
ссылку можно на оригинальную статью?
Страница 160 его книги, написано. Осталось найти книгу и скачать :)

По Фаулеру Doctrine - это антипаттерн Anemic Domain Model.
С другой стороны ActiveRecord - это тоже антипаттерн, как нарушающий первый принцип SOLID.
 

Absinthe

жожо
ссылку можно на оригинальную статью?
у меня есть сомнения
У меня сомнений нет, т.к. это высказывание истинно.
Активная запись очень похожа на шлюз записи данных (Row Data Gateway). Принципиальное отличие между
ними состоит в том, что шлюз записи данных содержит только логику доступа к базе данных, в то время как
активная запись содержит и логику доступа к данным, и логику домена.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
понятно, спасибо за цитату. я AR с доменной логикой не люблю смешивать
 

Absinthe

жожо
понятно, спасибо за цитату. я AR с доменной логикой не люблю смешивать
AR = Row Data Gateway + бизнес-логика.
Если AR без бизнес-логики, то это не AR :)

Если ты делаешь анемичные модели, то виновник — Doctrine?
Либо я не до конца понял определение Anemic Domain Model, либо кто-то с доктриной иначе работает.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
это напоминает мне христианство:
  • AR - плохо потому что доменная логика при расширении неизбежно нарушает SOLID
  • убираем из AR доменную логику - плохо, потому что анемичная модель нарушает инкапсуляцию данных с их обработкой
  • Doсtrine и hibernate - плохо потому что это тоже анемичная модель
  • TableDataGateway - кошерно, но никакой автоматизации и кодогенерации, так что сиди и дебажь sql на каждую банальную выборку по связи
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Либо я не до конца понял определение Anemic Domain Model, либо кто-то с доктриной иначе работает.
Doctrine использует рефлексию, она не требует сеттеры, геттеры (JavaBean), никто не заставляет выкидывать из твоей модели логику. Ну да, примеры из мануалов — это anemic domain model, но мы же взрослые люди, мы знаем что пишут в мануалах. :)
 

MiksIr

miksir@home:~$
Всегда считал, что датамапер позволяет решить проблему нарушения solid, так и не нарушает инкапсуляции, т.е. нет анемичной модели. Не?

Кстати, вот почему 95% людей на собеседовании про инкапсуляцию говорят, что это мол для защиты данных, что бы кто-то что-то не изменил случайно в модели и все такое. Где этому учат?...
 

Вурдалак

Продвинутый новичок
Всегда считал, что датамапер позволяет решить проблему нарушения solid, так и не нарушает инкапсуляции, т.е. нет анемичной модели. Не?
Тут про «инкапсуляцию» речь идёт в 2-х разных контекстах:
  1. DM (persistence ignorance, separation of concerns) vs AR (нарушение SRP, тесная связь с хранилищем, проникновение деталей реализации в domain layer -> нарушение инкапсуляции);
  2. Anemic Domain Model vs Rich Domain Model. С anemic я вижу 2 неприятных момента, связанных между собой: anemic не отвечает за своё состояние, т.е. это примерно как если бы мы могли создать new DateTime('foo') и нам бы приходилось постоянно бегать за валидатором, чтобы понимать, что дата имеет смысл; второй неприятный момент — это размытие логики с моделью по различным сервисам, отсутствие какого-то «человеческого» API, наличие тупых setter'ов и getter'ов позволит мне выполнить новое бизнес требование, возможно, с нарушением каких-то других, но я этого могу не узнать. Грубо говоря, а что если я в каким-нибудь случае переименую заблокированного пользователя? В каком-то сервисе будет проверка «if ($user->isBanned())», а я у себя забуду: я нарушу бизнес-требование, звучащее как «заблокированные пользователя не должны подвергаться изменениям», причем без rich такое требование по сути никакими тестами нельзя покрыть. С rich будет иначе: $user->rename('Foo') выкинет BannedUserModificationException.
 
Последнее редактирование:

MiksIr

miksir@home:~$
Но AR как таковой вроде бы не нарушает инкапсуляции? Да, модель оказывается забита логикой сохранения, связью с хранилищем, рождает кучу лишних зависимостей, но эта куча остается вся касательно данных модели? Данные и бизнес-логика остается же в одной модели.
Тогда как анемичная модель часто прямое нарушение инкапсуляции за счет выноса бизнес-логики из модели в сервисный слой. А сеттеры/геттеры - это уже вторично, их наличие как бы не признак анемичности ;)
Про доктрину я просто не знаю, не пользовался.
 

Вурдалак

Продвинутый новичок
Но AR как таковой вроде бы не нарушает инкапсуляции?
В привычном для меня понимании, в AR есть кишки работы с хранилищем: table definition, SQL-запросы. Знание про хранилище на этом уровне — это нарушение инкапсуляции. Знание о хранилище должно быть инкапсулировано в отдельный объект. С точки зрения модели мы ничего не знаем про детали реализации: почему нам должен быть интересен SQL на этом уровне? Почему я должен создавать таблицу, чтобы тестировать свой код? Почему я должен выгребать бизнес-логику из деталей реализации?

Тогда как анемичная модель
Почему ты противопоставляешь AR и анемичную модель? Что-то мешает тебе выносить бизнес-логику из AR-модели и делать её anemic? Что-то мешает тебе делать rich model с DM? Почему ты сравниваешь яблоко со стулом?

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