jdavidbakr / replaceable-model
为Eloquent模型添加'RePLACE'和'INSERT IGNORE'查询能力
Requires
- php: >=7.2.5
- illuminate/support: ^7.0|^8.0|^9.0
- symfony/http-foundation: >=5.0.7
Requires (Dev)
- orchestra/testbench: 5.x-dev|6.x-dev|7.x-dev
- phpunit/phpunit: ^8.4|^9.5
README
Eloquent默认模型在大多数情况下都很不错,但如果你的数据库表有额外的约束,你可能会遇到竞争条件,这时标准的update()调用会失败。例如,想象以下表结构
id auto increment
user_id
widget_id
date
其中每个用户每天只能有一个小部件,因此在user_id和date上有一个唯一约束。现在在你的界面中,你有一个使用ajax调用更新此表中条目的表单,这可能包括根据选择的条目删除项目。因为表单提交的是,比如,整整一个月的小部件,你不想逐个插入 - 你想执行单个插入查询。所以你的PHP代码中可能如下所示
Model::where('user_id',$user_id)->delete(); Model::insert($inserts);
这实际上在内部执行以下操作
delete from table where user_id = A; insert into table (user_id, widget_id, date) values (A, B, C) ...
如果你陷入竞争条件,你可能会得到以下结果
delete from table where user_id = A; -- process #1 delete from table where user_id = A; -- process #2 insert into table (user_id, widget_id, date) values (A, B, C) ... -- process #1 insert into table (user_id, widget_id, date) values (A, B, C) ... -- process #2 - Exception!
第二个插入将导致异常。如果第二个查询比第一个查询多一行,则该插入将永远丢失。
在Laravel之前,我通常会使用REPLACE或INSERT IGNORE命令来处理这种情况。REPLACE将在查询中的任何约束基础上执行删除和插入,而INSERT IGNORE将执行插入,但如果存在导致约束冲突的行,则这些行将不会被更新。如果你希望最后一个查询覆盖任何现有行,请使用REPLACE;如果你只想插入新行,请使用INSERT IGNORE。
因为这是MySQL的一个特定特性,Laravel不支持在Eloquent中使用它。标准的解决方案是执行原始查询。这是可以接受的,但是每次都要用绑定等构建这个查询有点麻烦,所以我决定创建一个Eloquent的特质,为我处理所有这些,并且可以通过与insert()相同的方式访问。
请注意,我这里没有使用Builder类。我直接扩展了Model类,因此你将无法像使用常规的insert()命令那样链式调用这些函数。这实际上只是一个用于解决我遇到的问题的宏。我欢迎任何可以解决你在此包中遇到的其他问题的pull requests。
'saving'和'saved'事件都会为这两个命令触发。
安装
通过Composer
$ composer require jdavidbakr/replaceable-model
使用
将特质应用到你的模型上以激活使用replace和insertIgnore的能力
class model extends Model { ... use \jdavidbakr\ReplaceableModel\ReplaceableModel ... }
然后构建你的插入数组,就像为insert()调用构建数组一样,并调用这两个函数中的一个
$inserts = [...]; \App\Model::replace($inserts); \App\Model::insertIgnore($inserts);
贡献
请参阅CONTRIBUTING和CONDUCT以获取详细信息。
安全性
如果你发现任何与安全性相关的问题,请通过电子邮件me@jdavidbaker.com联系,而不是使用问题跟踪器。
鸣谢
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。