V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
limingxinleo
V2EX  ›  PHP

小心 Laravel 中的 Model::increment

  •  
  •   limingxinleo · 2020-12-30 11:45:46 +08:00 · 1680 次点击
    这是一个创建于 1210 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Laravel v5.4.18 中的一个提交,导致的 BUG,因为添加了错误的单测,导致没办法轻易修改,这里提醒大家,使用时需要谨慎,以免采坑。

    commit

    pr#35748

    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 ORMWebSocket 服务端及客户端JSON RPC 服务端及客户端GRPC 服务端及客户端OpenTracing(Zipkin, Jaeger) 客户端Guzzle HTTP 客户端Elasticsearch 客户端Consul 、Nacos 服务中心ETCD 客户端AMQP 组件Nats 组件Apollo 、ETCD 、Zookeeper 、Nacos 和阿里云 ACM 的配置中心基于令牌桶算法的限流器通用连接池熔断器Swagger 文档生成Swoole TrackerBlade 、Smarty 、Twig 、Plates 和 ThinkTemplate 视图引擎Snowflake 全局 ID 生成器Prometheus 服务监控 等组件,省去了自己实现对应协程版本的麻烦。

    Hyperf 还提供了 基于 PSR-11 的依赖注入容器注解AOP 面向切面编程基于 PSR-15 的中间件自定义进程基于 PSR-14 的事件管理器Redis/RabbitMQ 消息队列自动模型缓存基于 PSR-16 的缓存Crontab 秒级定时任务Sessioni18n 国际化Validation 表单验证 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5281 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 01:24 · PVG 09:24 · LAX 18:24 · JFK 21:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.