PHP: __ toString () ve json_encode () birlikte iyi oynamak değil

5 Cevap php

Ben garip bir sorun haline çalıştırmak ve bunu düzeltmek için nasıl emin değilim. Ben JSON nesneleri tüm PHP uygulamaları birkaç sınıfları var. Sorunu burada 'bir illüstrasyon

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;

Bu ikinci çıkışı

[{},{}]

desired çıkışı olduğunda

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

Sorun benim için işi yapmak için __ toString () kanca dayanarak olmasıdır. Serialize ki json_encode () kullandığı __ toString () çağrı olmaz çünkü Ama yapamam. Bu iç içe geçmiş bir nesne ile karşılaştığında sadece ortak özelliklerini sadece serileştirir.

Yani soru o zaman bu hale: ben hem beni özellikleri belirleyiciler ve alıcılar kullanmanızı sağlar, ama aynı zamanda beni arzu JSON serileştirme davranışını almanızı sağlar json sınıflar için yönetilen arayüzü geliştirmek bir yolu var mı?

Açık değilse, burada __ set beri won't iş, () kanca sadece ilk atama için denir bir uygulama örneği

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;

Ben de böyle bir şey yapabileceğini varsayalım

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;

Ama sonra ayarlayıcıları kullanmak için, diğer geliştiricilerin titizlik güvenmek gerekir - bu çözüm ile programmtically bağlılığı zorlayamaz.

---> EDIT <---

Şimdi Rob Elsner tepkisinin bir testi ile

<?php

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

    public function getIterator()
    {
    	echo __METHOD__;
    }
}

echo json_encode( new a );

Eğer bu idam zaman, getIterator () yöntemi çağrılır hiç olmadığını görebilirsiniz.

5 Cevap

Geç cevap ama aynı sorun ile başkaları için yararlı olabilir.

In PHP < 5.4.0 json_encode nesneden herhangi bir yöntemi çağırmak değildir. Bu __ serialize, vb getIterator için geçerlidir ..

PHP> v5.4.0, ancak, yeni bir arabirim tanıttı JsonSerializable denirdi.

Bu temelde json_encode bu nesne üzerinde denir nesnenin davranışını denetler.


Fiddle (Şey, aslında PHP CodePad, ama aynı şey ...)

Example:

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

Outputs:

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

Içinde cevap değil PHP docs for json_encode?

Eklenmektedir değil özel özellikleri sorun haline çalıştırmak herkes için, sadece getIterator () yöntemi ile IteratorAggregate arabirimini uygulayabilirsiniz. GetIterator bir dizi () yöntemi, içine çıkışı dahil olmak üzere istediğiniz özellikleri ekleyin ve bunu geri döndürür.

Korumalı değişken yerine korunan kamu olsa bile, bu irade çıktı böyle tüm nesne beri istenilen girişi olmaz:

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

Yerine:

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

Bu büyük olasılıkla amacı yenilgi olacaktır, ama ben bir varsayılan gaz giderici orijinal sınıfta json dönüştürmek için daha meyilli olduğumu ve değerleri için doğrudan arama

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

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

Sonra bile A sınıfı gibi ek bir dizideki değerleri yok yuvalama, ne arzu onlarla yapabilirdi, ama json_decode kullanarak .. yine de biraz kirli hissediyor, ama çalışıyor.

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

(I çoğu sevmiyorum bile özelliklerini sıyırma + bana kirli hissettiriyor serileştirip) documentation bu soruna bazı tepkiler vardır.

PHP> v5.4.0 sizi JsonSerializable as described in the answer Tivie tarafından çağrılan arabirimini uygulayabilirsiniz.

PHP get_object_vars() from within the object itself and then feeds those to json_encode() . That is what I have done in the following example, using the __toString() yöntemini kullanan bir çözüm kullanabilirsiniz 5.4.0 .

Onlar bir dizi sanki biz nesne özellikleri üzerinde yineleme böylece Ayrıca IteratorAggregate interface, with its getIterator() bir uygulama yönteminin, dahildir.

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

?>

Bu kod aşağıdaki çıktıyı üretir:

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

Haklısınız __ toString () için hiçbir sebep yoktur, çünkü sınıf B, denir değil mi. Yani aramak için, bir döküm kullanabilirsiniz

class A
{
    protected $a;

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

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

Not: B yeni önce döküm (string) ... Bu B sınıfından) yöntemi (_toString arayacak, fakat klasik "çift kodlama" sorunlarla çalışır çünkü, ne istediğinizi almazsınız , dizi B sınıfı _toString () yöntemi kodlanmış, ve A sınıfı _toString () yöntemi again kodlanmış olacak çünkü.

Yani döküm, yani sonra sonuç çözme bir seçim var:

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

veya düz dizi döndürür B sınıfı bir toArray () yöntemini oluşturarak, dizisini almak için ihtiyacımız olacak. (Size (), yeni bir B yapamam -> toArray ();) doğrudan bir PHP kurucu kullanamazsınız, çünkü yukarıdaki satıra bazı kod katacak Yani böyle bir şey olabilir:

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