小心 Laravel 中的 Model::increment
Laravel v5.4.18 中的一个提交,导致的 BUG,因为添加了错误的单测,导致没办法轻易修改,这里提醒大家,使用时需要谨慎,以免采坑。
BUG 重现
1. increment extra 后再进行 save 操作,会执行两句SQL
我们先写一段没有 extra 数据的代码,进行测试
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->increment('count');
$model->save();
dump(DB::getQueryLog());
通过测试得知,以上代码只会生成两段 SQL,分别是
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `user_ext`.`updated_at` = ? where `id` = ?
然后让我们修改测试代码
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->increment('count', 1, [
'str' => uniqid()
]);
$model->save();
dump(DB::getQueryLog());
这时,会生成以下三段 SQL
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
update `user_ext` set `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
且第二段和第三段 SQL 中,str 的值是一致的。这个问题的主要原因,便是 extra 里的数据不会被同步到 original 中,就导致第二次 save 计算 dirty 的时候,出现了BUG。
2. getChanges 表现不一致
经过第一个 BUG 的重现,那么第二个问题也就很容易想到了,就是 getChanges 方法。
让我们继续编写代码测试
$model = UserExt::query()->find(101);
$model->increment('count');
dump($model->getChanges());
以上代码会输出以下数据,可见还是符合预期的
array:1 [▼
"count" => 4
]
让我们继续修改代码,在 increment 前增加一次赋值
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->str = uniqid();
$model->increment('count');
dump($model->getChanges());
dump(DB::getQueryLog());
会得到以下输出
array:2 [▼
"count" => 7
"str" => "5febf2dc798ed"
]
看似没有问题,但让我们检查一下 SQL
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `user_ext`.`updated_at` = ? where `id` = ?
却发现,并没有修改 str 的数据,那显然 getChanges 与预期不符。
实际上,increment 在设计上,并没有想要修改前面 setter 的数据,但这种情况下,我们 getChanges 便也不能把 str 算进来。
让我们继续修改代码
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->str = uniqid();
$model->increment('count');
dump($model->getChanges());
$model->save();
dump($model->getChanges());
dump(DB::getQueryLog());
两次 getChanges 输出如下
array:2 [▼
"count" => 9
"str" => "5febf3d6418e8"
]
array:2 [▼
"str" => "5febf3d6418e8"
"updated_at" => "2020-12-30 03:28:22"
]
可见两次 getChanges 中,str 的值是一致的。
save 的时候会把 updated_at 算进来,而
increment的时候是不会算updated_at,这里至少行为一致,可以作为后续的优化项。
输出的 SQL 如下
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
update `user_ext` set `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
写在最后
Hyperf 是基于 Swoole 4.5+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。
框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、WebSocket 服务端及客户端、JSON RPC 服务端及客户端、GRPC 服务端及客户端、OpenTracing(Zipkin, Jaeger) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul、Nacos 服务中心、ETCD 客户端、AMQP 组件、Nats 组件、Apollo、ETCD、Zookeeper、Nacos 和阿里云 ACM 的配置中心、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成、Swoole Tracker、Blade、Smarty、Twig、Plates 和 ThinkTemplate 视图引擎、Snowflake 全局ID生成器、Prometheus 服务监控 等组件,省去了自己实现对应协程版本的麻烦。
Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存、Crontab 秒级定时任务、Session、i18n 国际化、Validation 表单验证 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。
本作品采用《CC 协议》,转载必须注明作者和本文链接


关于 LearnKu
推荐文章: