[Решено] Проблемы с formModel

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

[Решено] Проблемы с formModel

Сообщение nepster »

Нашел себе проблему в следующем подходе, если стараться красиво разделять код yii2, например:

На каждую форму создать модель:

Код: Выделить всё

class PageForm extends \yii\base\Model
{
    /**
     * @var int
     */
    public $id;

    /**
     * @var string
     */
    public $url;

    /**
     * @var string
     */
    public $title;

    /**
     * @var string
     */
    public $text;

    /**
     * @var string
     */
    public $meta_title;

    /**
     * @var string
     */
    public $meta_description;

    /**
     * @var string
     */
    public $meta_keywords;

    /**
     * @var int
     */
    public $visible;

    /**
     * @var int
     */
    public $status;

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return array_merge((new Page())->attributeLabels(), [

        ]);
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        return [
            'create' => ['url', 'title', 'text', 'meta_title', 'meta_description', 'meta_keywords', 'visible', 'status'],
            'update' => ['url', 'title', 'text', 'meta_title', 'meta_description', 'meta_keywords', 'visible', 'status'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            // Обязательные поля
            [['title', 'text', 'url', 'status', 'visible'], 'required'],

            // Заголовок, описание, ключевые слова и название страницы
            [['meta_title', 'meta_description', 'meta_keywords'], 'filter', 'filter' => 'trim'],
            [['meta_title', 'meta_description', 'meta_keywords'], 'string'],

            ['title', 'filter', 'filter' => 'trim'],
            ['title', 'string', 'max' => 100],

            // Статус
            ['status', 'in', 'range' => array_keys(Page::getStatusArray())],
            ['visible', 'in', 'range' => array_keys(Yii::$app->formatter->booleanFormat)],

            // Текст [[text]]
            //['text', 'filter', 'filter' => 'htmlspecialchars'],
            ['text', 'string', 'max' => 10000],

            // Url адрес
            ['url', 'unique', 'targetAttribute' => 'url', 'targetClass' => Page::className()],
            ['url', 'string', 'max' => 16],
            ['url', 'match', 'pattern' => '/^[a-zA-Z0-9-_.]+$/'],
        ];
    }

    /**
     * Создать новую страницу
     * @return bool
     */
    public function create()
    {

    }

    /**
     * Редактировать страницу
     * @return bool
     */
    public function update()
    {

    }
}
Все четенько, все хорошо работает при создании, код разделен, а вот при редактировании появляется следующая проблема.
К примеру у меня в валидации есть unique проверка на url страницы. Если бы все было сделано через обычную модель унаследованную от актив рекорда, то там есть Attributes и OldAttributes, благодаря этому все бы порешалось.

В моем случае при редактировании нет таких возможностей, поэтому в действии редактирования нужно садить огород, а unique валидатор всегда плюется, что такое уже занято.

Подскажите пожалуйста как можно красиво разрулить без огорода ?
Последний раз редактировалось nepster 2015.09.12, 22:52, всего редактировалось 1 раз.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Проблемы с formModel

Сообщение nepster »

Вроде все разрулил. Собственно представлю решение для комментариев (привет Александр Зеленин):

Есть сущность статические страницы.
Задача максимально независимо написать код в рамках yii2.

Данная сущность делится на следующие составляющие:
- Page
- PageForm
- PageSearch
- PageQuery

Сейчас речь пойдет о PageForm. Данная модель наследует \yii\base\Model и содержит себе всю логику с формой страницы. Тоесть принимает данные, валидирует и сохраняет.

Почему это не представлено в отдельном сервисе ? Да потому, что за рамками yii2 мы это не как не используем, на прямую работа идет с компонентами yii2 и заканчивает работу с актив рекорд.

Собственно вся модель:

Код: Выделить всё

<?php

namespace app\modules\admin\models\form;

use app\modules\admin\models\Page;
use yii\helpers\ArrayHelper;
use Yii;

/**
 * Page Form
 * @package app\modules\admin\models\form
 */
class PageForm extends \yii\base\Model
{
    /**
     * @var int
     */
    public $id;

    /**
     * @var string
     */
    public $url;

    /**
     * @var string
     */
    public $title;

    /**
     * @var string
     */
    public $text;

    /**
     * @var string
     */
    public $meta_title;

    /**
     * @var string
     */
    public $meta_description;

    /**
     * @var string
     */
    public $meta_keywords;

    /**
     * @var int
     */
    public $visible;

    /**
     * @var int
     */
    public $status;

    /**
     * @var array
     */
    private $_oldAttributes = [];

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return array_merge((new Page())->attributeLabels(), [

        ]);
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        return [
            'create' => ['url', 'title', 'text', 'meta_title', 'meta_description', 'meta_keywords', 'visible', 'status'],
            'update' => ['url', 'title', 'text', 'meta_title', 'meta_description', 'meta_keywords', 'visible', 'status'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            // Обязательные поля
            [['title', 'text', 'url', 'status', 'visible'], 'required'],

            // Заголовок, описание, ключевые слова и название страницы
            [['meta_title', 'meta_description', 'meta_keywords'], 'filter', 'filter' => 'trim'],
            [['meta_title', 'meta_description', 'meta_keywords'], 'string'],

            ['title', 'filter', 'filter' => 'trim'],
            ['title', 'string', 'max' => 100],

            // Статус
            ['status', 'in', 'range' => array_keys(Page::getStatusArray())],
            ['visible', 'in', 'range' => array_keys(Yii::$app->formatter->booleanFormat)],

            // Текст [[text]]
            //['text', 'filter', 'filter' => 'htmlspecialchars'],
            ['text', 'string', 'max' => 10000],

            // Url адрес
            ['url', 'unique', 'targetAttribute' => 'url', 'targetClass' => Page::className(), 'filter' => function ($query) {
                $query->andWhere(['!=', 'id', $this->id]);
            }],
            ['url', 'string', 'max' => 16],
            ['url', 'match', 'pattern' => '/^[a-zA-Z0-9-_.]+$/'],
        ];
    }

    /**
     * Создать новую страницу
     * @return bool
     */
    public function create()
    {
        $model = new Page();
        $model->url = $this->url;
        $model->title = $this->title;
        $model->text = $this->text;
        $model->meta_title = $this->meta_title;
        $model->meta_description = $this->meta_description;
        $model->meta_keywords = $this->meta_keywords;
        $model->visible = $this->visible;
        $model->status = $this->status;
        return $model->save(false);
    }

    /**
     * Обновить страницу
     * @param Page $model
     * @return bool
     */
    public function update(Page $model)
    {
        $model->url = $this->url;
        $model->title = $this->title;
        $model->text = $this->text;
        $model->meta_title = $this->meta_title;
        $model->meta_description = $this->meta_description;
        $model->meta_keywords = $this->meta_keywords;
        $model->visible = $this->visible;
        $model->status = $this->status;
        return $model->save(false);
    }
}
 

Теперь собственно контроллер, который работает с данными:

Код: Выделить всё

    
    /**
     * Создать
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new PageForm(['scenario' => 'create']);
        $statusArray = Page::getStatusArray();

        if ($model->load(Yii::$app->request->post())) {
            if ($model->validate()) {
                if ($model->create()) {
                    Yii::$app->session->setFlash('success', 'Данные успешно сохранены.');
                } else {
                    Yii::$app->session->setFlash('danger', 'Возникла ошибка.');
                }
                return $this->redirect(['/admin/page/index']);
            } elseif (Yii::$app->request->isAjax) {
                Yii::$app->response->format = Response::FORMAT_JSON;
                return ActiveForm::validate($model);
            }
        }

        return $this->render('create', [
            'model' => $model,
            'statusArray' => $statusArray,
        ]);
    }

    /**
     * Редактировать
     * @param $id
     * @return mixed
     * @throws ForbiddenHttpException
     * @throws NotFoundHttpException
     */
    public function actionUpdate($id)
    {
        $page = Page::findOne($id);
        $statusArray = Page::getStatusArray();
        $model = new PageForm(['scenario' => 'update']);

        if (!$page) {
            throw new NotFoundHttpException('Страница не найдена.');
        }

        $model->id = $page->id;
        $model->setAttributes($page->getAttributes());

        if ($model->load(Yii::$app->request->post())) {
            if ($model->validate()) {
                if ($model->update($page)) {
                    Yii::$app->session->setFlash('success', 'Данные успешно сохранены.');
                } else {
                    Yii::$app->session->setFlash('danger', 'Возникла ошибка.');
                }
                return $this->refresh();
            } elseif (Yii::$app->request->isAjax) {
                Yii::$app->response->format = Response::FORMAT_JSON;
                return ActiveForm::validate($model);
            }
        }

        return $this->render('update', [
            'model' => $model,
            'statusArray' => $statusArray,
        ]);
    }
 
Собственно и профит. Вся логика для модели инкапсулирована в одну модель (formModel).

Кстате по поводу контроллеров в Yii2, эти штуки ну никак не предназначены для отдельных компонентов, тоесть если взять любой внешний модуль yii2, и нарисовать задачу как-то нестандартно его кастомизировать, то все контроллеры отправляются в топку. Но это уже отдельная тема.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Проблемы с formModel

Сообщение zelenin »

сказать честно, не вижу разницы, кроме наличия методов create/update.

Код: Выделить всё

$model->url = $this->url;
        $model->title = $this->title;
        $model->text = $this->text;
        $model->meta_title = $this->meta_title;
        $model->meta_description = $this->meta_description;
        $model->meta_keywords = $this->meta_keywords;
        $model->visible = $this->visible;
        $model->status = $this->status;
        return $model->save(false); 
эту бы простыню заменил на перебор свойств с присвоением одноименным свойствам модели.
это избавит от некрасивого забора и универсализирует в случае добалвения нового свойства.
velikj_programer
Сообщения: 7
Зарегистрирован: 2015.07.18, 16:01

Re: Проблемы с formModel

Сообщение velikj_programer »

Зачем так громоздко? Почему бы внутри update() модели формы ни написать:
if ($this->validate()) {
$model->setAttributes($this->getAttributes());
return $model->save(false);
}
return false;

Тогда в контроллере будет только:
if ($model->load(Yii::$app->request->post()) and $model->update($page)) Yii::$app->session->setFlash('success', 'Данные успешно сохранены.');
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Проблемы с formModel

Сообщение nepster »

zelenin писал(а):сказать честно, не вижу разницы, кроме наличия методов create/update.

Код: Выделить всё

$model->url = $this->url;
        $model->title = $this->title;
        $model->text = $this->text;
        $model->meta_title = $this->meta_title;
        $model->meta_description = $this->meta_description;
        $model->meta_keywords = $this->meta_keywords;
        $model->visible = $this->visible;
        $model->status = $this->status;
        return $model->save(false);  
эту бы простыню заменил на перебор свойств с присвоением одноименным свойствам модели.
это избавит от некрасивого забора и универсализирует в случае добалвения нового свойства.
Так разница в том, инкапсулировали кусок логики отвечающий за работу с формой в отдельную модель.

Я думал об этом. Это этот пример можно конечно улучшить, но при этом для меня проще когда все свойства на виду. Все наглядно видно и можно что-то подшаманить.

velikj_programer писал(а):Зачем так громоздко? Почему бы внутри update() модели формы ни написать:
if ($this->validate()) {
$model->setAttributes($this->getAttributes());
return $model->save(false);
}
return false;

Тогда в контроллере будет только:
if ($model->load(Yii::$app->request->post()) and $model->update($page)) Yii::$app->session->setFlash('success', 'Данные успешно сохранены.');
А представьте, что у вас есть более жирная сущность, и в зависимости от пользователей и каких-либо действий нужно выполнять разную логику.
В результате когда вы пишите весь код в одной моделе, по началу это очень удобно, но потом получаются следующие моменты:
1) Божественный класс. Который отвечает за все все все.
2) Жирная модель, видел даже по 14000 строк кода.
3) Изменяя такой класс вы рискуете поломать пол системы.
4) Я только изучаю тесты, но походу такую бодягу не протестировать.

Поэтому есть такая штука, как разделение приложения на слои. Почитайте вот это: viewtopic.php?f=28&t=31100
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проблемы с formModel

Сообщение ElisDN »

Код: Выделить всё

['url', 'unique', 'targetAttribute' => 'url', 'targetClass' => Page::className(), 'filter' => function (ActiveQuery $query) {
    $query->andWhere(['<>', 'id', $this->id]);
}],
Последний раз редактировалось ElisDN 2015.09.13, 00:36, всего редактировалось 1 раз.
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Проблемы с formModel

Сообщение lynicidn »

Код: Выделить всё

public function pull()
{
    $this->setAttributes($this->pullAttributes(), false);
}

public function pullAttributes()
{
    return $this->record->attributes;
}
public function push()
{
    $this->record->attributes = $this->pushAttributes();
}

public function pushAttributes()
{
    return $this->attributes;
}

работает примерно так

$attributes = parent::pullAttributes();
$dt = new DateTime($attributes['birthday'])
$attributes['birthday'] = $dt->format('blabla')
а в пуш обратно конертируй/разбивай на 3 инпута (год, число, месяц) - любые махинации вообщем
имена функций не запатентованы и можно оставить как есть :roll:
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Проблемы с formModel

Сообщение lynicidn »

кстати я один наверное из немногих кто тебя понимает,, верным путем идешь, такая структура уже показала себя хорошо при разделении приложения на веб сущности, еще плюс такого подхода это универсальные таблицы, к примеру есть 2 сущности каменты постов и каменты новостей, к примеру, таблица одна, ар модель одна, а сущность работающая с рекордом разная, PostComment и NewsComment обе работают с ар Comment, все масштабируемо и прозрачно, я правда пошел дальше еще и реляционные делаю поддержку ;)
velikj_programer
Сообщения: 7
Зарегистрирован: 2015.07.18, 16:01

Re: Проблемы с formModel

Сообщение velikj_programer »

nepster писал(а): А представьте, что у вас есть более жирная сущность, и в зависимости от пользователей и каких-либо действий нужно выполнять разную логику.
В результате когда вы пишите весь код в одной моделе, по началу это очень удобно, но потом получаются следующие моменты:
1) Божественный класс. Который отвечает за все все все.
2) Жирная модель, видел даже по 14000 строк кода.
3) Изменяя такой класс вы рискуете поломать пол системы.
4) Я только изучаю тесты, но походу такую бодягу не протестировать.
Я говорил про PageForm. Она отвечает только за текущую форму и ещё вариации.
И указал как сделать более тоньше её и контроллер.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Проблемы с formModel

Сообщение nepster »

velikj_programer писал(а):
nepster писал(а): А представьте, что у вас есть более жирная сущность, и в зависимости от пользователей и каких-либо действий нужно выполнять разную логику.
В результате когда вы пишите весь код в одной моделе, по началу это очень удобно, но потом получаются следующие моменты:
1) Божественный класс. Который отвечает за все все все.
2) Жирная модель, видел даже по 14000 строк кода.
3) Изменяя такой класс вы рискуете поломать пол системы.
4) Я только изучаю тесты, но походу такую бодягу не протестировать.
Я говорил про PageForm. Она отвечает только за текущую форму и ещё вариации.
И указал как сделать более тоньше её и контроллер.
Вы указали на уже пройденный путь. Так сказать скользкая дорожка, которая манит удобством, но скрывает серьезные последствия.


lynicidn писал(а):кстати я один наверное из немногих кто тебя понимает,, верным путем идешь, такая структура уже показала себя хорошо при разделении приложения на веб сущности, еще плюс такого подхода это универсальные таблицы, к примеру есть 2 сущности каменты постов и каменты новостей, к примеру, таблица одна, ар модель одна, а сущность работающая с рекордом разная, PostComment и NewsComment обе работают с ар Comment, все масштабируемо и прозрачно, я правда пошел дальше еще и реляционные делаю поддержку ;)
Я честно скажу, что я занимаю веб разработкой очень давно, но посколько я ленивая жопа и не люблю читать, я очень много лет делал кругу и даже никогда не задумывался за архитектуру. Даже лет 7 назад я уже мог написать что угодно на php, но каждый раз когда я смотрел на код, мне хотелось спрыгнуть с высотки (ну с парашют конечно).

Потом, когда я понял, что что-то тут не так, я пересел на фреймворки и только сейчас заставляю себя читать всякие полезные штуки по разработке архитектуре и тп. Вот собственно и пытаюсь развиваться и применять хорошие подходы в yii2.

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

ElisDN писал(а):

Код: Выделить всё

['url', 'unique', 'targetAttribute' => 'url', 'targetClass' => Page::className(), 'filter' => function (ActiveQuery $query) {
    $query->andWhere(['<>', 'id', $this->id],
}],  
Благодарю. Проверим.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Проблемы с formModel

Сообщение nepster »

ElisDN писал(а):

Код: Выделить всё

['url', 'unique', 'targetAttribute' => 'url', 'targetClass' => Page::className(), 'filter' => function (ActiveQuery $query) {
    $query->andWhere(['<>', 'id', $this->id],
}],

Честно говоря не понял в чем разница.
http://www.mysql.ru/docs/man/Comparison_Operators.html

вроде псевдонимы.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проблемы с formModel

Сообщение ElisDN »

nepster писал(а):Честно говоря не понял в чем разница.
В условии, что id при проверке не равен id текущей записи. Чтобы unique не плевалось на саму себя при редактировании.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: [Решено] Проблемы с formModel

Сообщение nepster »

так != тоже самое что и <>. Я даже проверил и работают одинаково.
Ответить