× 警告!旧版文档已经暂停维护,请查看新版文档。点击前往新版文档

在__get/__set魔术方法里不得产生协程切换

原因

参考PHP7内核剖析

Note: 如果类存在__get()方法,则在实例化对象分配属性内存(即:properties_table)时会多分配一个zval,类型为HashTable,每次调用__get($var)时会把输入的$var名称存入这个哈希表,这样做的目的是防止循环调用,举个例子:

public function __get($var) { return $this->$var; }

这种情况是调用__get()时又访问了一个不存在的属性,也就是会在__get()方法中递归调用,如果不对请求的$var作判断则将一直递归下去,所以在调用__get()前首先会判断当前$var是不是已经在__get()中了,如果是则不会再调用__get(),否则会把$var作为key插入那个HashTable,然后将哈希值设置为:guard |= IN_ISSET,调用完__get()再把哈希值设置为:guard &= ~IN_ISSET。

这个HashTable不仅仅是给__get()用的,其它魔术方法也会用到,所以其哈希值类型是zend_long,不同的魔术方法占不同的bit位;其次,并不是所有的对象都会额外分配这个HashTable,在对象创建时会根据 ***zend_class_entry.ce_flags*** 是否包含 ***ZEND_ACC_USE_GUARDS*** 确定是否分配,在类编译时如果发现定义了__get()、__set()、__unset()、__isset()方法则会将ce_flags打上这个掩码。

协程切换出去后,下次调用将会被判断为循环调用 此问题为PHP特性所致,与PHP开发组沟通后仍暂时无解

建议

自己实现get/set方法显式调用

原始问题链接

#2625