Cобытие Model::EVENT_ON_LOAD

Уже исправленные репорты или принятые предложения
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

Было бы удобно, если добавить событие Model::EVENT_ON_LOAD.

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

class Model extends Component
{
    //...
    public function load($data, $formName = null)
    {
        $this->trigger(self::EVENT_ON_LOAD, new OnLoadEvent([
            'data' => $data,
        ]));
        //...
    }
    //...
 
Это даст возможность форматировать атрибуты специальным образом только для вывода на форму, потому что, как правило, метод Model::load() вызывается в эшенах с пользовательским редактированием модели.

Наиболее распространенный случай - это атрибуты с датой-временем которые в БД хранятся в int-формате, в обычный html выводятся в различном виде через \yii\i18n\Formatter, а в поля формы должны попадать только в строго определенном формате (типа: 'Y-m-d H:i:s'), и в этом же формате проходить валидацию.

Простой пример поведения форматирующего время:

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

class EditableTimestampBehavior extends \yii\base\Behavior
{
    public function events()
    {
        return [ActiveRecord::EVENT_ON_LOAD => 'onLoad'];
    }

    public function onLoad()
    {
        $this->owner->published_at = date('Y-m-d H:i:s', $this->owner->published_at);
        $this->owner->on(ActiveRecord::EVENT_BEFORE_INSERT, [$this, 'beforeSave']);
        $this->owner->on(ActiveRecord::EVENT_BEFORE_UPDATE, [$this, 'beforeSave']);
    }
    
    public function beforeSave()
    {
        $this->owner->published_at =
          DateTime::createFromFormat('Y-m-d H:i:s', $this->owner->published_at)
            ->getTimestamp();
    }
} 
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

KiTE писал(а):Было бы удобно, если добавить событие Model::EVENT_ON_LOAD. Это даст возможность форматировать атрибуты специальным образом только для вывода на форму, потому что, как правило, метод Model::load() вызывается в эшенах с пользовательским редактированием модели.
load присваивает значения атрибутам после отправки формы, как ты собрался использовать его для "вывода на форму" ?
KiTE писал(а):Наиболее распространенный случай - это атрибуты с датой-временем которые в БД хранятся в int-формате, в обычный html выводятся в различном виде через \yii\i18n\Formatter, а в поля формы должны попадать только в строго определенном формате (типа: 'Y-m-d H:i:s'), и в этом же формате проходить валидацию.
Это мягко говоря бред. Аргументирую. Форматирование даты для сохранения делается по событию beforeSave и никак не раньше, если ты сделаешь это в load, а дальше форма не пройдет валидацию, тебе придется обратно конвертировать дату из int'а для формы. Во вторых сама дата указанная пользователем может быть не валидна и тогда в форму ты в лучшем случае вернешь пустую строку вместо того что ввел пользователь, а он так и не поймет, что же он сделал не правильно. Аналогично и для вывода, можно сразу сконвертировать дату по событию afterFind.
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

SiZE писал(а):load присваивает значения атрибутам после отправки формы, как ты собрался использовать его для "вывода на форму" ?.
Типовой пример из кода контроллера:

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

if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // ...
}
 
То есть load() выполнится в любом случае независимо от того пришли данные или нет. Нужно отследить сам факт этого вызова чтобы перевести модель в редактируемое через форму состояние.
SiZE писал(а):Это мягко говоря бред. Аргументирую. Форматирование даты для сохранения делается по событию beforeSave и никак не раньше, если ты сделаешь это в load, а дальше форма не пройдет валидацию, тебе придется обратно конвертировать дату из int'а для формы. Во вторых сама дата указанная пользователем может быть не валидна и тогда в форму ты в лучшем случае вернешь пустую строку вместо того что ввел пользователь, а он так и не поймет, что же он сделал не правильно. Аналогично и для вывода, можно сразу сконвертировать дату по событию afterFind.
Вероятно я не точно выразился. Попробую "на пальцах" :).

В модели есть атрибут "published_at" (предположительно дата-время публикации чего-то там). В базе он хранится в int-формате, потому как это наиболее удобный тип для форматирования и вывода во вьюв. Но, также, этот атрибут должен редактироваться пользователем через форму.

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

// Загружаем модель из базы. В  published_at текущее значение 1445427075
$model = Post::findOne($id);
if (
    // Ловим событие  EVENT_ON_LOAD и форматируем published_at.
    // Теперь в published_at значение "2015-10-21 14:31:15" и установлен обработчик на обратное преобразование
    $model->load(Yii::$app->request->post()) &&
    // Здесь мы валидируем форму.
    // Если общая валидация проходит успешно, то выполнится обработчик beforeSave,
    // который вернет пользовательское значение published_at  в int-формат перед записью в базу.
    // Если валидация не пройдена, то в форму попадет то, что ввел пользователь, с соответствующим сообщением об ошибке.
    // А до обратного преобразование через beforeSave дело не дойдет потому, что не пройден этап валидации.
    $model->save()
) {
    //...
}
// Рендерим форму, в которую попадет либо предварительно отформатированный published_at == "2015-10-21 14:31:15",
// либо пользовательский ввод из формы не прошедший валидацию.
 
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

Я понял что ты хочешь. Это делается просто.

Вариант со сценарием

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

// Контроллер actionCreate или actionUpdate
$model->scenario = 'userEdit';

// Модель
public function getPublishedAt()
{
   if ($this->scenario == 'userEdit') return \Yii::$app->formatter->asDate($this->published_at, 'php:d.m.Y');
}

protected function beforeSave()
{
    $this->published_at =  DateTime::createFromFormat('d.m.Y', $this->published_at)->getTimestamp();
    return true;
}
Вариант более универсальный. Вообще я думаю на гитхабе даже есть поведение которое это делает с учетом формата даты из конфига.

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

// Модель
public function afterFind()
{
   $this->published_at = \Yii::$app->formatter->asDate($this->published_at, 'php:d.m.Y');
}

protected function beforeSave()
{
    $this->published_at =  DateTime::createFromFormat('d.m.Y', $this->published_at)->getTimestamp();
    
    return true;
}
А то что ты предлагаешь довольно не наглядно и не целесообразно. Туда сюда преобразовывать, сам потом запутаешься.
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

Не все так однозначно.

На мой взгляд форматировать дату из int в человеко-читаемый формат нужно именно в представлении. Зашивать форматирование в метод модели не гибко. В разных вьювах может понадобиться разный формат (длинный, короткий, относительный, etc.). Чего не скажешь про вывод в форму, там нужен фиксированный формат, чтобы его можно было покрыть правилом валидации.

Делать преобразование в afterFind - а в чем тогда смысл того что в базе дата в int-формате, если во вьюве прийдется делать двойное преобразование?
Обработчик beforeSave нужен в любом случае, но бихейвиор выглядит куда универсальнее чем прямое переопределение этого метода в коде модели.

Я привел упрощенный пример бихейвиора специально чтобы было проще понять мысль. Его можно сделать универсальнее, и тогда подключение в модель могло бы выглядеть так:

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

public function behaviors()
{
    return [
        'editableTimestamp' => [
            'class' => 'EditableTimestampBehavior',
            'attributes' => [
                'published_at' => 'Y-m-d H:i:s',
            ],
        ],
    ];
}
 
Но пока из бихейвиора невозможно перехватить Model::load() - это не возможно.

Кроме того, преобразование дат - не единственный кейс, мне приходилось сталкиваться и с другими.
И, собственно, преобразование форматов для пользовательского редактирования - не единственный случай где можно было бы использовать обработку Model::load() из бихейвиора.
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

Вариант со сценарием также не сильно поможет, потому как сценарий устанавливается после EVENT_AFTER_FIND. Плюс нужно учитывать и новую модель.

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

$model = Post::findOne($id); // Тут сработал EVENT_AFTER_FIND
$model->scenario = 'userEdit'; // Уже не имеет смысла
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

KiTE писал(а):Вариант со сценарием также не сильно поможет, потому как сценарий устанавливается после EVENT_AFTER_FIND
Изображение

Пересмотри еще раз внимательно примеры и не изобретай велосипед. У меня на этом все.
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

SIZE давай без ЧСВ?..
Вот серьезно, меньше всего хочется ввязываться в спор кто умнее.
Интересует исключительно техническое обсуждение вопроса.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

KiTE писал(а):SIZE давай без ЧСВ?
Ты подумай почему тебе больше никто ничего не ответил в теме.
Аватара пользователя
KiTE
Сообщения: 112
Зарегистрирован: 2012.04.12, 14:47

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение KiTE »

Может потому что это ветка "Баг-репорты и предложения", а не "Вопросы и ответы", и у нее целевая аудитория меньше?..
Если бы я не знал альтернатив, то спросил бы именно там.
Впрочем, я тебя услышал. Если нечего сказать по существу, проходи мимо.
Nerf
Сообщения: 780
Зарегистрирован: 2015.01.29, 00:37

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение Nerf »

В базе он хранится в int-формате, потому как это наиболее удобный тип для форматирования и вывода во вьюв.
Сомнительный довод, подстраивать архитектуру БД под удобство вывода. В mysql гораздо удобней datetime.

Можно get/set использовать, а форме выводить виртуальный атрибут. Отчасти решит вашу проблему.
И не понятно зачем нужно менять входные данные после загрузки. load() загружает внешние данные, хотелось бы их иметь в том виде в котором они пришли.
Чего не скажешь про вывод в форму, там нужен фиксированный формат, чтобы его можно было покрыть правилом валидации.
Т.е. вы хотите написать фиксированное преобразование формат1 -> формат2. Что делать, если появится кейс, когда будет другая форма с другим форматом? Наверное лучше покрывать входные данные в rules() + сценарии, как вам и написали.

Я лично не вижу реального кейса, когда это нужно.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение zelenin »

KiTE писал(а):На мой взгляд форматировать дату из int в человеко-читаемый формат нужно именно в представлении. Зашивать форматирование в метод модели не гибко.
первая здравая мысль в ветке)
Теперь делаем так:

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

$model = Post::findOne(1);
...
$viewModel = new PostViewModel($model);
$this->render('view, ['viewModel' => $viewModel]);

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

$formattedDate = $viewModel->getDate(); 
и все у нас правильно.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение zelenin »

ребят, опять попытка из того, что сделано неправильно, сделать красиво.
Разрабочтики yii часто в моделях используют методы-хелперы, которые специальным образом форматируют данные ДЛЯ ОТОБРАЖЕНИЯ ВО ВЬЮХЕ. Но модель вообще ничего не знает и не должна знать о вьюхе. Модель может использоваться во многих местах с разным отображением даты - сколько этих хелперов мы должны сделать? А как быть с предложением в этой ветке? какой формат выбрать для преобразования?
Хотя все уже давно придумано - во вьюшку передается DTO - data transfer object (объект, переносящий данные) - с отформатированными для данной вьюшки данными.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

Удобней сделать единый вью хелпер для проекта и форматировать в соответствии с версткой и шаблонами.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение zelenin »

SiZE писал(а):Удобней сделать единый вью хелпер для проекта и форматировать в соответствии с версткой и шаблонами.
у yii семь бед - на все один ответ)
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение samdark »

Задача форматирования решается вне модели. EVENT_ON_LOAD добавлять не будем.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение SiZE »

zelenin писал(а):
SiZE писал(а):Удобней сделать единый вью хелпер для проекта и форматировать в соответствии с версткой и шаблонами.
у yii семь бед - на все один ответ)
А чем лучше семь моделей, когда надо дату в одном формате вывести? Избыточность добавляет универсальности, но реально всюду это делать не стоит, ПМСМ.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение zelenin »

SiZE писал(а):
zelenin писал(а):
SiZE писал(а):Удобней сделать единый вью хелпер для проекта и форматировать в соответствии с версткой и шаблонами.
у yii семь бед - на все один ответ)
А чем лучше семь моделей, когда надо дату в одном формате вывести? Избыточность добавляет универсальности, но реально всюду это делать не стоит, ПМСМ.
я не меряю код количеством моделей. Сначала думаю как правильнее и удобнее, а потом мысли воплощаю в коде. А правильно во вьюшку передавать статические данные - данные, не обладающие поведением, набор скаляров. А разработчик, который не знает, что ты какой-то статический метод завел для форматирования, напишет его еще раз и свой. Видя же ViewModel - конвертер AR-модели для view, он сразу догадается в единости подхода на всем сайте.
andrei.obuhovski
Сообщения: 610
Зарегистрирован: 2015.07.16, 10:50

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение andrei.obuhovski »

zelenin писал(а):ребят, опять попытка из того, что сделано неправильно, сделать красиво.
Разрабочтики yii часто в моделях используют методы-хелперы, которые специальным образом форматируют данные ДЛЯ ОТОБРАЖЕНИЯ ВО ВЬЮХЕ. Но модель вообще ничего не знает и не должна знать о вьюхе. Модель может использоваться во многих местах с разным отображением даты - сколько этих хелперов мы должны сделать? А как быть с предложением в этой ветке? какой формат выбрать для преобразования?
Хотя все уже давно придумано - во вьюшку передается DTO - data transfer object (объект, переносящий данные) - с отформатированными для данной вьюшки данными.
Ну тогда по вашей логике и экранирование во ВьюМодели нужно делать, а шаблонизаторы выкинуть.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Cобытие Model::EVENT_ON_LOAD

Сообщение zelenin »

andrei.obuhovski писал(а):
zelenin писал(а):ребят, опять попытка из того, что сделано неправильно, сделать красиво.
Разрабочтики yii часто в моделях используют методы-хелперы, которые специальным образом форматируют данные ДЛЯ ОТОБРАЖЕНИЯ ВО ВЬЮХЕ. Но модель вообще ничего не знает и не должна знать о вьюхе. Модель может использоваться во многих местах с разным отображением даты - сколько этих хелперов мы должны сделать? А как быть с предложением в этой ветке? какой формат выбрать для преобразования?
Хотя все уже давно придумано - во вьюшку передается DTO - data transfer object (объект, переносящий данные) - с отформатированными для данной вьюшки данными.
Ну тогда по вашей логике и экранирование во ВьюМодели нужно делать, а шаблонизаторы выкинуть.
это не моя логика. это правила хорошего тона программирования, неведомые программистам yii.
экранирование - безопасность вывода данных. вьюмодел - доставка данных во вьюшку. SRP.
Ответить