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クラスへのマネージインターフェイスを開発して、プロパティにセッターとゲッターを使用できると同時に、希望する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; bar <!> 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"}]
それはおそらくあなたの目的に反しますが、デフォルトのゲッターを使用して元のクラスの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();を実行できないため)。 p>
$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );