Laravel Eloquent 5.1 эффективно обновляет связанную модель

Я ищу чистое решение для запроса и изменения динамических свойств моей модели Eloquent, хранящихся в другой таблице.

Моя основная модель User. User может иметь несколько UserVariable. При загрузке модели через UserRepository я могу или не могу с нетерпением загружать переменные.

Чего я хочу добиться, так это того, что UserVariable можно изменять в памяти и автоматически сохранять, когда и только когда сохраняется User.

Вот мое решение (удалены ненужные части), которое работает, но не является ни элегантным, ни масштабируемым:

/**
 * @property boolean $isCommentingEnabled
 */
class User extends \Illuminate\Database\Eloquent\Model
{
    public function variables()
    {
        return $this->hasMany('UserVariable', 'var_userid', 'user_id');
    }

    public function getIsCommentingEnabledAttribute()
    {
        foreach ($this->variables as $variable) {
            if ($variable->name == UserVariable::IS_COMMENTING_ENABLED) {
                return (boolean) $variable->value;
            }
        }
        return false;
    }

    public function setIsCommentingEnabledAttribute($enabled)
    {
        foreach ($this->variables as $variable) {
            if ($variable->name == UserVariable::IS_COMMENTING_ENABLED) {
                $variable->value = $enabled ? 1 : 0;
                return;
            }
        }
        $this->variables()->add(new UserVariable([
                'name'  => UserVariable::IS_COMMENTING_ENABLED,
                'value' => $enabled ? 1 : 0,
        ]));
    }
}

/**
 * @property-read int $id Unique ID of the record.
 * @property string $name Must be one of the constants in this class.
 * @property int $userId
 * @property string $value
 */
class UserVariable extends EloquentModel {}

class UserRepository
{
    public function findById($id)
    {
        return User::with('variables')->find($id);
    }

    public function saveUser(User $user)
    {
        return $user->push();
    }
}

Это решение явно не масштабируется. Если бы у пользователя было 5+ переменных, кода было бы много, даже если бы я извлек циклы.

Я подозреваю, что в Laravel должно быть короткое и чистое решение, чтобы просто вывести User UserVariable по имени или получить новое, если оно не существует, изменить его значение и вернуть его в модель. Когда вызывается User::push(), он сохраняется автоматически. Сделанный.

Я ищу что-то вроде

$user->variables()->where('name', UserVariable::IS_COMMENTING_ENABLED)->first()->value
        = $enabled ? 1 : 0;

Но вышеописанное не работает должным образом, потому что использует БД, а не модель. Любая помощь приветствуется.

Примечание. Я работаю над большой базой устаревшего кода, поэтому об изменении структуры БД пока не может быть и речи.


person Dávid Veszelovszki    schedule 31.07.2015    source источник


Ответы (1)


К сожалению, нет встроенного способа сделать это. К счастью, не так уж плохо просто реализовать.

У нас есть модель с отношением ->variables.

class User extends \Illuminate\Database\Eloquent\Model {
    public function variables() {
        return $this->hasMany('UserVariable', 'var_userid', 'user_id');
    }

Затем нам нужно создать метод, который сообщает нам, загрузили ли мы уже указанное отношение, и один, чтобы загрузить их, если мы этого не сделали.

    protected function hasLoadedVariables() {
        $relations = $this->getRelations();
        return array_key_exists('variables', $relations);
    }

    protected function loadVariablesIfNotLoaded() {
        if(!$this->hasLoadedVariables()) {
            $this->load('variables');
        }
    }

Эти методы позволяют нам создавать общие геттеры и сеттеры для UserVariables.

Получатель проверяет, загружено ли отношение variables, а затем возвращает первое значение с соответствующим ключом.

    protected function getVariable($key) {
        $this->loadVariablesIfNotLoaded();
        foreach($this->variables as $variable) {
            if($variable->key === $key) {
                return $variable->value;
            }
        }
        return null;
    }

Сеттер немного сложнее, потому что мы хотим обновить существующую модель UserVariable, если она существует, и создать ее, если нет, и в любом случае сохранить изменение в отношении ->variables, но не в БД. ($this->variables->push($variable) сохраняет в отношение, но не в базу данных).

    protected function setVariable($key, $value) {
        $this->loadVariablesIfNotLoaded();
        foreach($this->variables as $k => $variable) {
            if($variable->key === $key) {
                $variable->value = $value;
                return;
            }
        }
        // We didn't find an existing UserVariable so we create one.
        $variable = new UserVariable;
        $variable->user_id = $this->id;
        $variable->key = $key;
        $variable->value = $value;
        $this->variables->push($variable);
    }

Метод Laravel ->push() по умолчанию не сохраняет записи, которые не существуют в базе данных, но мы можем переопределить его, чтобы сделать это.

    public function push(array $options = []) {
        if($this->hasLoadedVariables()) {
            foreach($this->variables as $variable) {
                if(!$variable->exists) {
                    $this->variables()->save($variable);
                } else if($variable->isDirty()) {
                    $variable->save();
                }
            }
        }
        parent::push();
    }

Наконец, со всем этим мы можем добавить определенные геттеры и сеттеры для отдельных атрибутов.

    protected function setIsCommentingEnabledAttribute($enabled) {
        $this->setVariable(UserVariable::IS_COMMENTING_ENABLED, $enabled);
    }

    protected function getIsCommentingEnabledAttribute($enabled) {
        $this->getVariable(UserVariable::IS_COMMENTING_ENABLED);
    }
}

Чтобы использовать это, просто напишите код, как обычно.

$user = User::find(123);
$user->is_commenting_enabled; // Calls the getter, no DB calls
$user->is_commenting_enabled = 1; // Calls the setter, no DB calls
$user->push(); // Saves the user and any variables which have been added or changed.
person Joshua David    schedule 02.01.2016