Pregunta

Me he encontrado con un problema extraño y no estoy seguro de cómo solucionarlo. Tengo varias clases que son todas 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 la salida deseada es

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

El problema es que confiaba 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 solo las propiedades públicas.

Entonces, la pregunta se convierte en esta: ¿Hay alguna manera de desarrollar una interfaz administrada para las clases JSON que me permita usar setters y getters para propiedades, pero también me permita obtener el comportamiento de serialización JSON que deseo?

Si eso no está claro, aquí hay un ejemplo de una implementación que no funcionará , ya que el gancho __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 entonces tendría que confiar en la diligencia de los otros desarrolladores para usar los setters: no puedo forzar la adhesión programáticamente con esta solución.

--- > EDITAR <---

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.

¿Fue útil?

Solución

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

Salidas :

  

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

Otros consejos

¿No es su respuesta en los documentos PHP para json_encode ?

  

Para cualquiera que se haya encontrado con el problema de que no se agreguen propiedades privadas, simplemente puede implementar la interfaz IteratorAggregate con el método getIterator (). Agregue las propiedades que desea incluir en la salida en una matriz en el método getIterator () y devuélvalas.

En PHP > v5.4.0 puede implementar la interfaz llamada JsonSerializable como se describe en < a href = "https://stackoverflow.com/a/16203263/193617"> la respuesta por Tivie .

Para aquellos de nosotros que usamos PHP < 5.4.0 puede 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 lanzo el objeto como una cadena, obtengo una representación codificada JSON.

También se incluye una implementación de la interfaz IteratorAggregate , con su getIterator() , para 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

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

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

En lugar de:

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

Lo más probable es que anule su propósito, pero estoy más inclinado a convertir a json en la clase original con un captador predeterminado y solicitando 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 lo hace tu clase A, pero usando json_decode ... todavía se siente algo 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 esto problema (incluso si no me gustan la mayoría de ellos, serializar + quitar las propiedades me hace sentir sucio).

Tienes razón, __toString () para la clase B no se llama, porque no hay 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: el (string) emitido antes de los nuevos B ... esto llamará al método _toString () de la clase B, pero no obtendrá lo que desea, ¡porque se encontrará con el clásico quot; doble codificación " problemas, porque la matriz está codificada en el método de clase B _toString (), y se codificará nuevamente en el método de clase A _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 necesitará obtener la matriz, creando un método toArray () en la clase B que devuelve la matriz recta. Lo que agregará algo de código a la línea anterior porque no puede usar un constructor PHP directamente (no puede hacer un nuevo B () - & Gt; toArray ();) Entonces podría tener algo como:

$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top