У меня возникла странная проблема, и я не знаю, как ее исправить. У меня есть несколько классов, которые представляют собой 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, который позволяет мне использовать сеттеры и getters для свойств, но также позволяет мне получить поведение сериализации 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;
Но тогда я должен был бы полагаться на усердие других разработчиков, чтобы использовать сеттеры – я не могу принудительно придерживаться программы с этим решением.
—> EDIT <—
Теперь с ответом Роба Элснера
<?php class a implements IteratorAggregate { public $foo = 'bar'; protected $bar = 'baz'; public function getIterator() { echo __METHOD__; } } echo json_encode( new a );
Когда вы выполняете это, вы можете видеть, что метод getIterator () никогда не вызывается.
Поздние ответы, но могут быть полезны для других с той же проблемой.
В PHP <5.4.0 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 "}]"
Не ваш ответ в документах PHP для json_encode ?
Для тех, кто сталкивается с проблемой добавления частных свойств, вы можете просто реализовать интерфейс IteratorAggregate с помощью метода getIterator (). Добавьте свойства, которые вы хотите включить в вывод, в массив метода getIterator () и верните его.
В PHP> v5.4.0 вы можете реализовать интерфейс JsonSerializable
как описано в ответе Tivie .
Для тех из нас, кто использует PHP <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 в исходном классе с использованием getter по умолчанию и призывая к значениям непосредственно
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 ); } }
В документации есть некоторые ответы на эту проблему (даже если мне не нравится большинство из них, сериализация + удаление свойств заставляет меня чувствовать себя грязным).
Вы правы __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 ); } }
Примечание: приведение строки (string) перед новыми B … это вызовет метод _toString () класса B, но он не даст вам то, что вы хотите, потому что вы столкнетесь с классическими проблемами «двойного кодирования» , потому что массив закодирован в методе класса B _toString (), и он будет снова закодирован в методе класса A _String ().
Таким образом, есть выбор декодирования результата после трансляции, то есть:
$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() );