PHP: __toString () и json_encode () не очень хорошо играют вместе - PullRequest
10 голосов
/ 31 декабря 2008

У меня возникла странная проблема, и я не уверен, как ее исправить. У меня есть несколько классов, которые все PHP-реализации объектов JSON. Вот иллюстрация проблемы

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;

Но тогда мне пришлось бы полагаться на усердие других разработчиков в использовании сеттеров - я не могу программно придерживаться этого решения.

---> РЕДАКТИРОВАТЬ <--- </strong>

Теперь с проверкой ответа Роба Эльснера

<?php

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

    public function getIterator()
    {
        echo __METHOD__;
    }
}

echo json_encode( new a );

Когда вы выполните это, вы увидите, что метод getIterator () никогда не вызывается.

Ответы [ 5 ]

26 голосов
/ 25 апреля 2013

Поздние ответы, но могут быть полезны для других с такой же проблемой.

В PHP <5.4.0 </em> json_encode не вызывает никакой метод из объекта. Это действительно для getIterator, __serialize и т.д ...

В PHP> v5.4.0, однако, был представлен новый интерфейс под названием JsonSerializable .

По сути, он управляет поведением объекта, когда для этого объекта вызывается json_encode.


Fiddle (Ну, на самом деле это 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) "[{" foo ":" bar "}, {" foo ":" bar "}]"

2 голосов
/ 24 октября 2013

В PHP> v5.4.0 вы можете реализовать интерфейс под названием JsonSerializable, как описано в ответ от Tivie .

Для тех из нас, кто использует PHP <5.4.0, вы можете использовать решение, которое использует <a href="http://php.net/manual/en/function.get-object-vars.php" rel="nofollow noreferrer">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
2 голосов
/ 31 декабря 2008

Разве ваш ответ не в PHP документах для json_encode ?

Для тех, кто столкнулся с проблемой не добавления частных свойств, вы можете просто реализовать интерфейс IteratorAggregate с помощью метода getIterator (). Добавьте свойства, которые вы хотите включить в вывод, в массив метода getIterator () и верните его.

0 голосов
/ 31 декабря 2008

Вы правы, __toString () для класса B не вызывается, потому что для этого нет причин. Таким образом, чтобы назвать это, вы можете использовать приведение

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 ... это вызовет метод _toString () класса B, но не даст вам того, что вы хотите, потому что вы столкнетесь с классическим "двойным" проблемы кодирования », поскольку массив закодирован в методе класса _toString () класса B, и он будет закодирован снова в методе _toString () класса А.

Так что есть выбор декодирования результата после приведения, то есть:

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

или вам понадобится получить массив, создав метод toArray () в классе B, который возвращает прямой массив. Это добавит некоторый код в строку выше, потому что вы не можете напрямую использовать конструктор PHP (вы не можете сделать новый B () -> toArray ();), поэтому вы можете получить что-то вроде:

$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
0 голосов
/ 31 декабря 2008

Даже если ваша защищенная переменная была общедоступной, а не защищенной, у вас не будет нужного ввода, так как при этом будет выведен весь объект следующим образом:

[{"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 );
    }
}

Тогда вы можете делать с ними все, что пожелаете, даже вкладывая значения в дополнительный массив, как это делает ваш класс А., но используя 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 );
    }
}

В документации есть некоторые ответы на эту проблему (даже если большинство из них мне не нравятся, при сериализации + удалении свойств я чувствую себя грязным).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...