PHP:__ toString()和json_encode()不能很好地融合在一起
-
03-07-2019 - |
题
我遇到了一个奇怪的问题,我不知道如何解决它。我有几个类都是JSON对象的PHP实现。这是'问题的说明
class A
{
protected $a;
public function __construct()
{
$this->a = array( new B, new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
class B
{
protected $b = array( 'foo' => 'bar' );
public function __toString()
{
return json_encode( $this->b );
}
}
$a = new A();
echo $a;
此输出是
[{},{}]
所需的输出
时[{"foo":"bar"},{"foo":"bar"}]
问题是我依靠__toString()钩子来为我做我的工作。但它不能,因为json_encode()使用的序列化不会调用__toString()。遇到嵌套对象时,它只是序列化公共属性。
那么,问题就变成了这样:有没有办法可以开发一个JSON类的托管接口,它既可以让我使用setter和getter来获取属性,也可以让我获得我想要的JSON序列化行为?
如果不清楚,这里是一个不会工作的实现示例,因为__set()挂钩只被调用初始赋值
class a
{
public function __set( $prop, $value )
{
echo __METHOD__, PHP_EOL;
$this->$prop = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->foo = 'bar';
$a->foo = 'baz';
echo $a;
我想我也可以做这样的事情
class a
{
public $foo;
public function setFoo( $value )
{
$this->foo = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->setFoo( 'bar' );
echo $a;
但后来我不得不依赖其他开发人员的勤奋来使用这些解决方案 - 我不能强制使用这个解决方案加入程序。
<!><强> --- GT;编辑<!> lt; ---
现在测试一下Rob Elsner的回应
<?php
class a implements IteratorAggregate
{
public $foo = 'bar';
protected $bar = 'baz';
public function getIterator()
{
echo __METHOD__;
}
}
echo json_encode( new a );
执行此操作时,您可以看到不会调用getIterator()方法。
解决方案
答案较晚,但对于遇到同样问题的其他人可能会有用。
在 PHP中<!> lt; 5.4.0 json_encode
不会从对象调用任何方法。这对getIterator,__ serialize等有效......
在PHP中<!> gt;然而,v5.4.0引入了一个名为 JsonSerializable 的新界面。
当在该对象上调用 <=> 时,它基本上控制了对象的行为。
小提琴 (嗯,实际上是PHP CodePad,但同样的事情......)
示例强>:
class A implements JsonSerializable
{
protected $a = array();
public function __construct()
{
$this->a = array( new B, new B );
}
public function jsonSerialize()
{
return $this->a;
}
}
class B implements JsonSerializable
{
protected $b = array( 'foo' => 'bar' );
public function jsonSerialize()
{
return $this->b;
}
}
$foo = new A();
$json = json_encode($foo);
var_dump($json);
<强>输出:强>
string(29)<!> quot; [{<!> quot; foo <!> quot;:<!> quot; bar <!> quot;},{<!> quot; foo <!> quot ;:<!>; <!>; <!> QUOT酒吧QUOT}] QUOT;
其他提示
json_encode的 PHP文档中的答案不是的?
对于遇到未添加私有属性问题的任何人,您只需使用getIterator()方法实现IteratorAggregate接口即可。将要包含在输出中的属性添加到getIterator()方法的数组中并返回它。
在PHP中<!> gt; v5.4.0你可以实现名为 JsonSerializable
的界面,如< Tivie 答案 >
对于我们这些使用PHP <!> lt; 5.4.0您可以使用 get_object_vars()
从对象本身内部,然后将其提供给 json_encode()
。这就是我在下面的例子中使用__toString()
方法所做的,所以当我将对象转换为字符串时,我得到一个JSON编码表示。
还包括 IteratorAggregate
界面的实现,它的 getIterator()
方法,以便我们可以迭代对象属性如果他们是阵列。
<?php
class TestObject implements IteratorAggregate {
public $public = "foo";
protected $protected = "bar";
private $private = 1;
private $privateList = array("foo", "bar", "baz" => TRUE);
/**
* Retrieve the object as a JSON serialized string
*
* @return string
*/
public function __toString() {
$properties = $this->getAllProperties();
$json = json_encode(
$properties,
JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
);
return $json;
}
/**
* Retrieve an external iterator
*
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return \Traversable
* An instance of an object implementing \Traversable
*/
public function getIterator() {
$properties = $this->getAllProperties();
$iterator = new \ArrayIterator($properties);
return $iterator;
}
/**
* Get all the properties of the object
*
* @return array
*/
private function getAllProperties() {
$all_properties = get_object_vars($this);
$properties = array();
while (list ($full_name, $value) = each($all_properties)) {
$full_name_components = explode("\0", $full_name);
$property_name = array_pop($full_name_components);
if ($property_name && isset($value)) $properties[$property_name] = $value;
}
return $properties;
}
}
$o = new TestObject();
print "JSON STRING". PHP_EOL;
print "------" . PHP_EOL;
print strval($o) . PHP_EOL;
print PHP_EOL;
print "ITERATE PROPERTIES" . PHP_EOL;
print "-------" . PHP_EOL;
foreach ($o as $key => $val) print "$key -> $val" . PHP_EOL;
print PHP_EOL;
?>
此代码生成以下输出:
JSON STRING
------
{"public":"foo","protected":"bar","private":1,"privateList":{"0":"foo","1":"bar","baz":true}}
ITERATE PROPERTIES
-------
public -> foo
protected -> bar
private -> 1
privateList -> Array
即使您的受保护变量是公共的而不是受保护的,您也没有所需的输入,因为这将输出整个对象,如下所示:
[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]
而不是:
[{"foo":"bar"},{"foo":"bar"}]
它很可能会破坏你的目的,但我更倾向于使用默认的getter转换为原始类中的json并直接调用值
class B
{
protected $b = array( 'foo' => 'bar' );
public function __get($name)
{
return json_encode( $this->$name );
}
}
然后你可以随心所欲地使用它们,甚至将值嵌套在类A的附加数组中,但是使用json_decode ..它仍然感觉有些脏,但是有效。
class A
{
protected $a;
public function __construct()
{
$b1 = new B;
$b2 = new B;
$this->a = array( json_decode($b1->b), json_decode($b2->b) );
}
public function __toString()
{
return json_encode( $this->a );
}
}
在文档中,有一些回复问题(即使我不喜欢大多数问题,序列化+剥离属性会让我觉得很脏)。
你是对的,没有调用类B的__toString(),因为没有理由。所以要调用它,你可以使用强制转换
class A
{
protected $a;
public function __construct()
{
$this->a = array( (string)new B, (string)new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
注意:在新B之前的(字符串)强制转换...这将调用B类的_toString()方法,但它不会得到你想要的东西,因为你将遇到经典的<! > quot; double encoding <!> quot;问题,因为数组是在B类_toString()方法中编码的,它将在A类_toString()方法中再次编码。
因此可以选择在演员表后解码结果,即:
$this->a = array( json_decode((string)new B), json_decode((string)new B) );
或者你需要通过在B类中创建一个返回直数组的toArray()方法来获取数组。这将在上面的行中添加一些代码,因为你不能直接使用PHP构造函数(你不能做一个新的B() - <!> gt; toArray();)所以你可以有类似的东西:
$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );