Yii2和thinkphp5中一个小差异造成bug

2019-08-12 15:27:50 浏览数 (1)

考虑一个场景,一个函数需对相同表进行多次查询,多次查询中有部分查询条件相同。对于这种情况,Yii2和thinkphp5的实现方式要格外小心。在Yii2中,可以直接使用clone 复用共同的查询条件,但是thinkphp5的话,必须把相同条件再重复写一次。

例如,需要查询总有效文章数,以及今日发布有效文章数。

Yii2 版本

thinkphp5版本

如果在thinkphp5中使用clone会发生什么?

  • 1 clone model

执行过程没有报错,但是实际上是否真的正确呢?看一下执行的语句:

查询最终的执行时通过model类中的getQuery()方法获得的query对象执行的。所有的查询条件最终都绑定在query对象当中。

可以看出,clone model 之后,内部query其实还是同一个。虽然是在clone出来不同的两个model添加查询条件,但是最终都是添加在相同的query当做。 所以第一条语句就会有所有的查询条件。第二条语句没有任何条件的原因是因为query执行完之后,会把查询条件情空。

  • clone query

既然clone model不行,那直接clone内部query呢?

执行过程,抛出SQLSTATE[HY000]: General error: 2031错误信息,看看内部解析成什么样的语句了:

代码语言:javascript复制
SELECT count(*) FROM `test` WHERE `status` = 1 
AND `is_delete` = 0;


SELECT count(*) FROM `test` WHERE 
`status` = :where_AND_status AND 
`is_delete` = :where_AND_is_delete AND
 `create_at` BETWEEN :where_AND_create_at_between_1 
AND :where_AND_create_at_between_2

初步认为是参数没有绑定上去。应该也是query内部引用了一个对象,对象在clone之后与原有对象是一个地址引用。通过一步一步断点输出,确认在$this->builder->select($options);之后获得了bind数据。因此只需要解绑clone前后对象的builder属性即可完成query对象的复制。查看query对象的属性,只有builder,connection是对象,但是connection我们希望在整个请求中是一个单实例,所以没必要区分。

最终修改,新建query子类,添加__clone方法,指定clone后对新对象执行php $this->setBuilder();保证 clone之后的builder是一个新实例。

到此,对于一开始的使用场景,thinkphp5也可以使用clone完成

在这其中有几点需要注意:

  • 对象clone之后,其属性执行的是浅拷贝!!
  • __clone()方法的操作只对clone出来新对象有效!
  • 如果没做任何修改,thinkphp5中不要直接clone model,除非自己知道在干什么,否则容易参数bug,因为它不抛错误。

0 人点赞