sobrecarga overload métodos method magicos php json serialization

overload - php method constructor



PHP:__toString() y json_encode() no funcionan bien juntos (5)

Me encontré con un problema extraño y no estoy seguro de cómo solucionarlo. Tengo varias clases que son todas las implementaciones PHP de objetos JSON. Aquí ''una ilustración del problema

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;

El resultado de esto es

[{},{}]

Cuando el resultado deseado es

[{"foo":"bar"},{"foo":"bar"}]

El problema es que estaba confiando en el gancho __toString () para hacer mi trabajo por mí. Pero no puede, porque la serialización que usa json_encode () no llamará a __toString (). Cuando encuentra un objeto anidado, simplemente serializa las propiedades públicas solamente.

Entonces, la pregunta es: ¿hay alguna forma de desarrollar una interfaz administrada para las clases JSON que me permita usar setters y getters para las propiedades, pero también me permite obtener el comportamiento de serialización JSON que deseo?

Si no está claro, aquí hay un ejemplo de una implementación que no funcionará, ya que el enlace __set () solo se llama para la asignación inicial.

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;

Supongo que también podría hacer algo como esto

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;

Pero luego tendría que confiar en la diligencia de los otros desarrolladores para usar los incubadores. No puedo forzar la adhesión programmtically con esta solución.

---> EDIT <---

Ahora con una prueba de la respuesta de Rob Elsner

<?php class a implements IteratorAggregate { public $foo = ''bar''; protected $bar = ''baz''; public function getIterator() { echo __METHOD__; } } echo json_encode( new a );

Cuando ejecuta esto, puede ver que el método getIterator () nunca se invoca.


¿No es tu respuesta en los documentos de PHP para json_encode ?

Para cualquier persona que se haya topado con el problema de no agregar propiedades privadas, simplemente puede implementar la interfaz IteratorAggregate con el método getIterator (). Agregue las propiedades que desea incluir en el resultado a una matriz en el método getIterator () y devuélvala.


Incluso si su variable protegida fuera pública en lugar de protegida, no tendrá la entrada deseada, ya que generará todo el objeto de esta manera:

[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]

En lugar de:

[{"foo":"bar"},{"foo":"bar"}]

Lo más probable es que venza tu propósito, pero estoy más inclinado a convertir a json en la clase original con un getter predeterminado y solicitar los valores directamente

class B { protected $b = array( ''foo'' => ''bar'' ); public function __get($name) { return json_encode( $this->$name ); } }

Entonces podrías hacer con ellos lo que desees, incluso anidar los valores en una matriz adicional como tu clase A lo hace, pero usando json_decode ... todavía se siente un poco sucio, pero funciona.

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 ); } }

En la documentación hay algunas respuestas a este problema (incluso si no me gusta la mayoría de ellas, serializar + eliminar las propiedades me hace sentir sucio).


Tienes razón, __toString () para la clase B no se está llamando, porque no hay ninguna razón para hacerlo. Entonces, para llamarlo, puedes usar un reparto

class A { protected $a; public function __construct() { $this->a = array( (string)new B, (string)new B ); } public function __toString() { return json_encode( $this->a ); } }

Nota: la (cadena) del molde antes de las nuevas B ... esto llamará al método _toString () de la clase B, pero no le dará lo que quiere, porque se encontrará con los problemas clásicos de "codificación doble". , porque la matriz está codificada en el método B clase _toString () y se codificará nuevamente en el método A class _toString ().

Entonces, hay una opción de decodificar el resultado después del lanzamiento, es decir:

$this->a = array( json_decode((string)new B), json_decode((string)new B) );

o va a necesitar obtener la matriz, creando un método toArray () en la clase B que devuelve la matriz directa. Lo cual agregará algún código a la línea anterior porque no puede usar un constructor de PHP directamente (no puede hacer un nuevo B () -> toArray ();) Así que podría tener algo como:

$b1 = new B; $b2 = new B; $this->a = array( $b1->toArray(), $b2->toArray() );


Una respuesta tardía, pero podría ser útil para otras personas con el mismo problema.

En PHP <5.4.0 json_encode no llama a ningún método desde el objeto. Eso es válido para getIterator, __serialize, etc ...

En PHP> v5.4.0, sin embargo, se introdujo una nueva interfaz, llamada JsonSerializable .

Básicamente controla el comportamiento del objeto cuando se llama a json_encode en ese objeto.

Fiddle (Bueno, en realidad es PHP CodePad, pero lo mismo ...)

Ejemplo :

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);

Productos:

string (29) "[{" foo ":" bar "}, {" foo ":" bar "}]"


En PHP> v5.4.0 puede implementar la interfaz llamada JsonSerializable como se describe en la respuesta de Tivie .

Para aquellos de nosotros que usamos PHP <5.4.0 podemos usar una solución que emplea get_object_vars() desde el propio objeto y luego los alimenta a json_encode() . Eso es lo que hice en el siguiente ejemplo, usando el método __toString() , de modo que cuando __toString() el objeto como una cadena, obtengo una representación codificada JSON.

También se incluye una implementación de la interfaz getIterator() , con su método getIterator() , de modo que podamos iterar sobre las propiedades del objeto como si fueran una matriz.

<?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; ?>

Este código produce el siguiente resultado:

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