在JsonSerializable中编码克隆$ this

时间:2016-11-29 16:15:42

标签: php recursion clone stack-overflow

此简化案例导致PHP段错误(退出127):

class Datum implements \JsonSerializable{
  public function jsonSerialize(){
    return clone $this;
  }
}
echo json_encode(new Datum);

最后一行代码导致退出(127)。我无法在当前环境中检索任何堆栈。

同时,移除clone令牌可以正常工作。

有没有可能解释为什么会这样?

1 个答案:

答案 0 :(得分:1)

此代码会导致无限递归。

似乎PHP JSON模块以这种方式支持JsonSerializable(伪代码):

function json_encode($data){
    if($data instanceof JsonSerializable) return json_encode($data->jsonSerialize());
    else real_json_encode($data); // handling primitive data or arrays or pure data objects
}

如果你返回另一个JsonSerializable实例,json_encode将尝试再次序列化它,导致无限递归。

这适用于return $this;,但可能是由于json_encode实现的故意解决方法,当返回的对象相同时,即返回$this时,它直接转到真正的json_encode 。但是,自$a !== clone $a以来克隆对象不会发生这种情况。

参考

这个答案可以通过php-src的引用来支持。

// in php_json_encode_zval
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
    return php_json_encode_serializable_object(buf, val, options, encoder);
}

// in php_json_encode_serializable_object
if ((Z_TYPE(retval) == IS_OBJECT) &&
    (Z_OBJ(retval) == Z_OBJ_P(val))) {
    /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
    PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
    return_code = php_json_encode_array(buf, &retval, options, encoder);
} else {
    /* All other types, encode as normal */
    return_code = php_json_encode_zval(buf, &retval, options, encoder);
    PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
}

这些片段证明PHP将return $this;编码为数组(或作为非可序列化对象),而返回任何其他内容使Z_OBJ(retval) == Z_OBJ_P(val)为false,转到else块递归再次调用php_json_encode_zval

TL; DR,简单解决方案:return (array) $this;而不是clone $this;