防止Laravel同时发生模型突变的最佳方法

时间:2019-01-18 20:00:24

标签: php laravel

这可能是并发用户在同一应用程序中工作时最常见的问题之一。

用户A从数据库中获取一个模型,该模型在屏幕上显示给用户。他执行一些操作,并更新该对象的值。

在特定时间保存模型。用户A几乎不知道在他获取模型并保存修改后的模型之间,用户B已经获取了相同模型,并将其更改提交给数据库。结果:用户B的更改丢失。

我已经解决了很多相同的问题,通常这样做是这样的:

1 /向模型$version添加一个属性

2 /创建新模型后,我们设置$version = 1

3 /在获取模型时,显然也会获取version属性。

4 /保存修改的模型时,我只保存... where id = $model->id AND WHERE version = $model->version,同时更新$version = $version + 1

当该更新抛出数据库“没有记录更新”时,我仅使用id搜索模型;如果可以获取,我知道模型已经存在,但是版本不再匹配。然后,我向用户提出一个例外(说“某人或某物已经修改了模型”)

我相信这是确保始终更新对象的最新版本的唯一正确方法。显然,您也可以使用updated_at属性来比较您是否仍具有相同版本的对象,但是理论上其他用户可能仍对其进行了更新。

现在,我正在为如何在Laravel中设置此逻辑而苦苦挣扎。我想创建一个可以在模型上使用的特征,该特征基本上可以完成我在上面描述的工作,但是我在为应该做的事情而苦苦挣扎。

我知道我可以覆盖protected function setKeysForSaveQuery($query)以使其更新where id = $this->id and version = $this->version,但是当我这样做时,laravel不更新任何内容时,我没有得到允许我确定更新的异常。因版本不匹配而失败...

我希望避免在将保存内容封装到事务中的地方编写代码,并且每次保存模型时都必须验证版本;寻找一种通过包含特征将它们放入我的基本模型中的方法,以便它自动地起作用

任何输入将不胜感激。

-添加示例:

想象一下:

$modelcopy1 = App\Model::find(1);  // version = 1
$modelcopy2 = App\Model::find(1);  // version = 1

$modelcopy1->save(); // this should update version in database to 2
                     // also $modelcopy1->version should now hold 2

$modelcopy2->save(); // this should now throw an exception
                     // because copy 1 still references version 1
                     // database version is already at 2

因此,基本上,我需要->save()中的逻辑来验证version属性的模型值与当前数据库版本匹配,并且需要同步,以便在完成更新时,model version属性为已更新为最新版本。

保存应如下图所示:

UPDATE Model
SET version = version + 1, attr1 = :attr1, attr2 = :attr2, ...
WHERE version = $model->version
AND id = $model->id

如果UPDATE更新成功->确保$ model-> version已更新为新版本值

如果UPDATE更新失败,则从* WHERE id = $ model-> id的模型中进行选择

如果找到模型,则THROW ERROR'模型已被其他人更新'ELSE THROW ERROR'模型不存在'

我希望这是有道理的。我认为,这种防止锁定的机制应该成为任何“成人”框架的一部分,但是将其绑定到Laravel框架中似乎很难...

1 个答案:

答案 0 :(得分:0)

因此,您想编写一个特征来执行检查,以查看自收到当前模型以来是否已对模型进行了更新,并抛出可挂钩的内容以通知用户?我将研究制作所有模型都可以扩展的基础模型,并在该基础模型中使用模型事件。如果仅让所有模型扩展基本模型,则甚至不需要特征。参见下面的示例模型事件方法:

protected static function boot(){
    parent::boot();
    static::saving(function($thisModel){
        $changes = Model::where('version', $thisModel->version)
            ->where('id', $thisModel->id)->first()
        /** if changes->version matches this one, good, if not, set some
         *  property on the model that can be checked.   
         */
    }
}

在保存模型时会发生 ,如果您在方法中的任何时候return false;,保存都会失败,并且不会保存模型。

如果仍然要使用特征,请确保boot方法遵循以下格式:bootTraitName(),否则它将不会在实现类的启动时启动。这是一个棘手的问题,有很多不同的角度,但我希望至少给您一些考虑的问题。

编辑:我从没有在基本模型上使用过模型事件,所以不确定parent::boot();是否必要。可能要测试。

相关问题