OptimisticLock, behaviors и метод link()

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
von.hamster
Сообщения: 69
Зарегистрирован: 2013.06.06, 16:07

OptimisticLock, behaviors и метод link()

Сообщение von.hamster »

Доброго времени суток.

Столкнулся со следующими проблемами:

Yii Version: 2.0.48.1
PHP 8.2.8

Есть AR модель Product (для простоты поля guid (первичный ключ), file_guid, lock)
Есть вторая модель File, в которой хранятся данные файла (поля guid (первичный ключ), name...)

Есть собственный behavior для файлов:

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

<?php


namespace app\modules\file\behaviors;


use app\modules\file\models\File;
use yii\base\Behavior;
use yii\base\Event;
use yii\db\ActiveRecord;
use yii\db\AfterSaveEvent;
use yii\db\BaseActiveRecord;
use yii\web\UploadedFile;

/**
 * Class FileBehavior
 * @property ActiveRecord $owner
 * @package app\modules\file\behaviors
 */
class FileBehavior extends Behavior
{
    /**
     * eg:
     * [
     *      'attribute' => [
     *          'formName' => 'Model', // instance by name
     *          'formField' => 'field', // instance by name
     *          'group' => 'fileGroup',
     *      ]
     * ]
     * @var array
     */
    public array $fields = [];

    public function init(): void
    {
        parent::init();
        $fields = [];
        foreach ($this->fields as $attribute => $field) {
            $f = array_merge([
                'formField' => $attribute,
                'group' => '',
            ], $field);
            $fields[$attribute] = $f;
        }
        $this->fields = $fields;
    }

    public function events(): array
    {
        return [
            BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
            BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
            BaseActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
        ];
    }

    /**
     * @param AfterSaveEvent $event
     */
    public function afterSave(AfterSaveEvent $event): void
    {
        foreach ($this->fields as $attribute => $field) {
            $file = UploadedFile::getInstanceByName($field['formField']);
            if ($file && $file->error === UPLOAD_ERR_OK) {
                $file = File::upload($file, '', $field['group'], true);
                if ($file) {
                    if ($this->owner->$attribute) {
                        $this->owner->$attribute->delete();
                    }
                    $this->owner->link($attribute, $file);
                }
            }
        }
    }

    public function afterDelete(Event $event): void
    {
        foreach ($this->fields as $attribute => $field) {
            if (!empty($event->sender->$attribute)) {
                $event->sender->$attribute->delete();
            }
        }
    }
}
Определена связь и behavior:

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

Product extends \yii\db\ActiveRecord{
    public function behaviors(): array
    {
        return [
            'files' => [
                'class' => FileBehavior::class,
                'fields' => [
                    'file' => [
                        'formField' => 'Product[fileImage]',
                    ]
                ]
            ]
        ];
    }

    public function getImage(): ActiveQuery
    {
        return $this->hasOne(File::class, ['guid' => 'file_guid']);
    }
}

Проблема №1:
метод link($attribute, $file); вызывает событие EVENT_AFTER_UPDATE, который, соответственно повторно вызывает afterSave у Product.
Хотя в документации прописано:
The relationship is established by setting the foreign key value(s) in one model to be the corresponding primary key value(s) in the other model. The model with the foreign key will be saved into database without performing validation and without events/behaviors.
Картинка с ошибкой, где видно, что вызывается 2 раза:
https://von-hamster.ru/uploads/ext/%D0% ... %D0%B8.png
Вопрос в том - это я что-то не так использую, или это бага?

Проблема №2:
Связана с 1 - при использовании link и OptimisticLockBehavior - вторая выдает ошибку:
The object being updated is outdated.
https://von-hamster.ru/uploads/ext/%D0% ... -10-00.png
unknownby
Сообщения: 749
Зарегистрирован: 2019.11.05, 16:34
Контактная информация:

Re: OptimisticLock, behaviors и метод link()

Сообщение unknownby »

По проблеме 1.
В описании документации написано, что link сохраняет значение по вторичному ключу.
Т.е. выходит обычное упрощение, чтобы вместо указания поля из модели и какое значение туда передать, да ещё и вызвать метод save. Можно заменить методом link.

Может в доке имелось ввиду без ивентов той модели, которую во вторичный ключ вставляли? :D Хотя логично было бы не юзать ивенты от модели куда вставляем по fk
von.hamster
Сообщения: 69
Зарегистрирован: 2013.06.06, 16:07

Re: OptimisticLock, behaviors и метод link()

Сообщение von.hamster »

Понял проблему с OptimisticLockBehavior, в принципе, в документации все есть, но не сразу осознал, что не так.
Поясню:
Если добавить OptimisticLockBehavior к модели, то просто так ее сохранить нельзя (в смысле без выполнения запроса или переопределения параметра OptimisticLockBehavior::$value). Тоесть, в коде уже нельзя будет выполнить:

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

$model = MyModel::findOne(id);
$model->my_attribute = 'value';
$model->save();
Работать не будет (The object being updated is outdated.).
Необходимо, чтобы в запросе (Request::getBodyParam) был указан параметр из optimisticLock.
Либо нужно указать value в настройке OptimisticLockBehavior (значение или метод).
Как один из вариантов - для использования в форме - создать новую модель и наследовать ее от основной. При этом в новой уже указать OptimisticLockBehavior.
Ответить