xint0 / laravel-optimistic-locking
为Eloquent模型添加乐观锁功能。
Requires
- php: ^7.2|^8.0
- illuminate/database: ~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
Requires (Dev)
- ext-pdo_sqlite: *
- mockery/mockery: ^1.0.0
- orchestra/testbench: ~3.5.0|~3.6.0|~3.8.0|^4.0|^5.0|^6.0|^7.0|^8.0|^9.0
- phpunit/phpunit: ^7.0|^8.0|^9.0|^10.0
Replaces
README
为Eloquent模型添加乐观锁。
此包替换了 reshadman/laravel-optimistic-locking
。
安装
composer require xint0/laravel-optimistic-locking
兼容性
此包支持Laravel 5.5、5.6、5.7、5.8、6、7、8、9、10和11。
用法
基本用法
在你的模型中使用 \Reshadman\OptimisticLocking\OptimisticLocking
特性
<?php
use Reshadman\OptimisticLocking\OptimisticLocking;
class BlogPost extends Model {
use OptimisticLocking;
}
将可空的整数 lock_version
字段添加到模型的表中
<?php # migration to add lock version column
$schema->integer('lock_version')->unsigned()->nullable();
然后你就可以开始了,如果两个不同的进程 并发地 编辑了相同的资源,则会抛出以下异常
<?php
use Reshadman\OptimisticLocking\StaleModelLockingException;
你应该捕获上述异常,并根据你的业务逻辑正确处理。
在业务事务中维护lock_version
你可以在业务事务中跟踪锁版本,通过通知你的API或HTML客户端当前的版本
<input type="hidden" name="lock_version" value="{{$blogPost->lock_version}}"
并在控制器中
<?php
use Reshadman\OptimisticLocking\StaleModelLockingException;
// Explicitly setting the lock version
class PostController {
public function update($id)
{
$post = Post::findOrFail($id);
$post->lock_version = request('lock_version');
try {
$post->save();
// You can also define more implicit reusable methods in your model like Model::saveWithVersion(...$args);
// or just override the default Model::save(...$args); method which accepts $options
// Then automatically read the lock version from Request and set into the model.
} catch (StaleModelLockingException $exception) {
// Occurs when request lock version does not match value stored in database. Which means another user
// has already updated the model.
}
}
}
因此,如果有两个作者同时编辑相同的内容,你可以跟踪你的 读取状态,并要求第二个作者重写他的更改。
禁用和启用乐观锁
你可以禁用和启用特定实例的乐观锁
<?php
$blogPost->disableLocking();
//
//save model does not verify lock version
//
$blogPost->enableLocking();
//
// save model verifies lock version
//
默认情况下,当你使用 OptimisticLocking
特性在你的模型中时,乐观锁是启用的,为了改变默认行为,你可以将锁定严格设置为 false
<?php
use Illuminate\Database\Eloquent\Model;
use Reshadman\OptimisticLocking\OptimisticLocking;
class BlogPost extends Model
{
use OptimisticLocking;
protected $lock = false;
}
然后你可以启用它: $blogPost->enableLocking();
使用不同的列来跟踪版本
默认情况下,使用 lock_version
列来跟踪版本,你可以通过重写特性的以下方法来改变这一点
<?php
use Illuminate\Database\Eloquent\Model;
use Reshadman\OptimisticLocking\OptimisticLocking;
class BlogPost extends Model
{
use OptimisticLocking;
/**
* Name of the lock version column.
*
* @return string
*/
protected static function lockVersionColumn()
{
return 'track_version';
}
}
什么是乐观锁?
有关详细说明,请阅读 Martin Fowler的《企业应用架构模式》 的并发部分。
有两种方法可以解决通用的并发竞争条件
- 不允许其他进程(或用户)读取和更新同一资源(悲观锁)。
- 允许其他进程并发读取同一资源,但如果在其中一个进程更新资源之前其他进程已经更新了资源,则不允许进一步更新(乐观锁)。
Laravel允许如文档所述的悲观锁,此包允许你以类似Rails的方式拥有乐观锁。
乐观锁期间发生了什么?
每次你对资源(模型)执行更新操作时,表中的 lock_version
计数器字段都会增加 1
。如果你读取了一个资源,而另一个进程在你在读取之后更新了该资源,则真实版本计数器增加一个。如果当前进程尝试更新模型,则会抛出 StaleModelLockingException
,你应该根据你的业务逻辑处理竞争条件(合并、重试、忽略)。这很简单,只需将以下标准添加到 乐观锁模型 的更新查询中即可
<?php
$query->where('id', $this->id)
->where('lock_version', $this->lock_version)
->update($changes);
如果资源在您的更新尝试之前已被更新,则上述操作将简单地更新 无 记录,这意味着模型在当前尝试之前已被更新,或已被删除。
我们为什么不使用 updated_at
来跟踪更改?
因为它们在两个并发更新期间可能保持不变。
贡献
这是一个开源软件包,我们鼓励你通过以下方式做出贡献
- 通过以下任一渠道报告问题
- 发送邮件至:
laravel-optimistic-locking@xint0.org
。 - 在gitlab.com打开问题: https://gitlab.com/xint0-open-source/laravel-optimistic-locking/-/issues
- 发送邮件至:
- 在gitlab.com创建项目的分支并提交合并请求:https://gitlab.com/xint0-open-source/laravel-optimistic-locing/-/forks/new
与代码一起工作
项目使用Docker Compose定义开发中使用的容器。尽管不一定要使用Docker,但它可以使处理多个PHP版本变得更加容易。
在将git仓库分支克隆到本地开发环境后,您可以执行以下docker compose命令
安装依赖
docker compose run --rm php composer install
执行测试
docker compose run --rm php ./vendor/bin/phpunit
执行带覆盖率报告的测试
docker compose run --rm --env=XDEBUG_MODE=coverage php php ./vendor/bin/phpunit --coverage-text
执行PHPStan分析
docker compose run --rm phpstan
许可协议
MIT许可协议(MIT)。有关更多信息,请参阅许可文件。