Question

J'ai rencontré un problème étrange et je ne sais pas comment le résoudre. J'ai plusieurs classes qui sont toutes des implémentations PHP d'objets JSON. Voici une illustration du problème

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;

La sortie de ceci est

[{},{}]

Lorsque la sortie souhaitée est

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

Le problème est que je m'appuyais sur le hook __toString () pour effectuer mon travail à ma place. Mais cela ne peut pas, car la sérialisation utilisée par json_encode () n’appelle pas __toString (). Lorsqu'il rencontre un objet imbriqué, il ne fait que sérialiser les propriétés publiques.

La question devient alors la suivante: y at-il un moyen de développer une interface gérée pour les classes JSON qui me permette à la fois d’utiliser des sélecteurs et des getters pour les propriétés, mais qui me permette également d’obtenir le comportement de sérialisation JSON que je souhaite?

Si ce n'est pas clair, voici un exemple d'implémentation que ne fonctionnera pas , car le hook __set () n'est appelé que pour l'affectation initiale

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

Je suppose que je pourrais aussi faire quelque chose comme ç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;

Mais dans ce cas, je devrais me fier à la diligence des autres développeurs pour utiliser les configurateurs. Je ne peux pas forcer l'adhésion par programme avec cette solution.

--- > EDIT & Lt; ---

Maintenant, avec un test de la réponse de Rob Elsner

<?php

class a implements IteratorAggregate 
{
    public $foo = 'bar';
    protected $bar = 'baz';

    public function getIterator()
    {
        echo __METHOD__;
    }
}

echo json_encode( new a );

Lorsque vous l'exécutez, vous pouvez voir que la méthode getIterator () n'est jamais invoquée.

Était-ce utile?

La solution

Une réponse tardive, mais pourrait être utile pour les autres personnes ayant le même problème.

Dans PHP < 5.4.0 json_encode n'appelle aucune méthode à partir de l'objet. Ceci est valable pour getIterator, __serialize, etc ...

En PHP > La v5.4.0 a toutefois introduit une nouvelle interface appelée JsonSerializable .

Il contrôle essentiellement le comportement de l'objet lorsque <=> est appelé sur cet objet.

Fiddle (En réalité, c'est PHP CodePad, mais c'est la même chose ...)

Exemple :

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

Résultats:

  

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

Autres conseils

Votre réponse ne figure-t-elle pas dans la documentation PHP pour json_encode ?

  

Si vous rencontrez le problème de l'ajout de propriétés privées, vous pouvez simplement implémenter l'interface IteratorAggregate avec la méthode getIterator (). Ajoutez les propriétés que vous souhaitez inclure dans le résultat dans un tableau de la méthode getIterator () et renvoyez-le.

En PHP > v5.4.0, vous pouvez implémenter l'interface appelée JsonSerializable , comme décrit dans < a href = "https://stackoverflow.com/a/16203263/193617"> la réponse de Tivie .

Pour ceux qui utilisent PHP < 5.4.0, vous pouvez utiliser une solution utilisant get_object_vars() à partir de l'objet lui-même, puis il alimente json_encode() . C’est ce que j’ai fait dans l’exemple suivant, à l’aide de la méthode __toString(), de sorte que lorsque j’ai converti l’objet sous forme de chaîne, j’obtienne une représentation codée JSON.

Une implémentation de l'interface IteratorAggregate est également incluse, avec sa méthode getIterator() afin de pouvoir effectuer une itération sur les propriétés de l'objet si elles étaient un tableau.

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

?>

Ce code produit la sortie suivante:

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

Même si votre variable protégée était publique au lieu de protégée, vous n’auriez pas l’entrée désirée car cela produira l’objet entier comme ceci:

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

Au lieu de:

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

Cela ira probablement à l'encontre de votre objectif, mais je suis plus enclin à convertir en json dans la classe d'origine avec un getter par défaut et à appeler directement les valeurs

class B
{
    protected $b = array( 'foo' => 'bar' );

    public function __get($name)
    {
        return json_encode( $this->$name );
    }
}

Vous pouvez ensuite en faire ce que vous voulez, même en imbriquant les valeurs dans un tableau supplémentaire, comme le fait votre classe A, mais en utilisant 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 );
    }
}

Dans la documentation , vous trouverez des réponses à cette question. problème (même si je n’aime pas la plupart d’entre eux, la sérialisation et la suppression des propriétés me font me sentir sale).

Vous avez raison, la __toString () de la classe B n’est pas appelée car il n’ya aucune raison de le faire. Donc, pour l'appeler, vous pouvez utiliser un casting

class A
{
    protected $a;

    public function __construct()
    {
        $this->a = array( (string)new B, (string)new B );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

Remarque: la conversion (de chaîne) avant les nouveaux B ... cela appellera la méthode _toString () de la classe B, mais cela ne vous donnera pas ce que vous voulez, car vous rencontrerez le classique à nouveau dans la méthode A class _toString ().

Vous avez donc le choix entre décoder le résultat après la conversion, c'est-à-dire:

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

ou vous allez devoir obtenir le tableau, en créant une méthode toArray () dans la classe B qui renvoie le tableau droit. Ce qui ajoutera du code à la ligne ci-dessus car vous ne pouvez pas utiliser directement un constructeur PHP (vous ne pouvez pas créer de nouveau B () - & Gt; toArray ();). Vous pourriez donc avoir quelque chose comme:

$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top