PHP:__toString() و json_encode() لا يلعب بشكل جيد معا
-
03-07-2019 - |
سؤال
لقد واجهت مشكلة غريبة و انا غير متأكد من كيفية إصلاحه.لدي العديد من فئات كل 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().عندما واجه متداخلة كائن ببساطة يسلسل الممتلكات العامة فقط.
إذن السؤال ثم تصبح هذه:هل هناك طريقة يمكنني وضع تمكنت واجهة سلمان الطبقات أن كلا يتيح لي استخدام واضعي و حاصل على خصائص ، ولكن أيضا يسمح لي للحصول على سلمان التسلسل سلوك أنا الرغبة ؟
إذا هذا ليس واضحا ، وهنا مثال على تنفيذ ذلك لن العمل منذ __مجموعة() هوك فقط دعا الأولية المهمة
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;
ولكن بعد ذلك سوف تضطر إلى الاعتماد على الاجتهاد من مطورين آخرين إلى استخدام واضعي - لا أستطيع أن أجبر على الانضمام programmtically مع هذا الحل.
---> تحرير <---
والآن مع اختبار روب إيلسنر رد
<?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, __تسلسل ، الخ...
في PHP > v5.4.0 ومع ذلك واجهة جديدة استحدثت يسمى JsonSerializable.
فإنه يتحكم في الأساس سلوك الكائن عند json_encode
ويطلق على هذا الكائن.
كمان (في الواقع هو 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);
النواتج:
سلسلة(29) "[{"فو":"شريط"},{"فو":"شريط"}]"
نصائح أخرى
ليس الجواب في PHP مستندات json_encode?
لأي شخص الذين قد واجهت مشكلة الممتلكات الخاصة لا يتم إضافتها ، يمكنك ببساطة تنفذ في بلدان واجهة مع getIterator الأسلوب ().إضافة خصائص تريد إدراجها في الإخراج في صفيف في getIterator() طريقة وإعادته.
في PHP > v5.4.0 يمكنك تنفيذ واجهة تسمى JsonSerializable
كما هو موضح في الجواب قبل Tivie.
بالنسبة لنا باستخدام PHP < 5.4.0 يمكنك استخدام الحل الذي توظف get_object_vars()
من داخل الكائن نفسه ثم يغذي تلك json_encode()
.وهذا هو ما قمت به في المثال التالي باستخدام __toString()
طريقة بحيث عندما يلقي الكائن كسلسلة ، أحصل على سلمان ترميز التمثيل.
وشملت أيضا هو تنفيذ 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 في الطبقة الأصلية مع الافتراضي جالبة يدعو إلى القيم مباشرة
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 );
}
}
في الوثائق هناك بعض الردود على هذه المشكلة (حتى إذا كنت لا ترغب في معظمها ، التسلسلية + تجريد خصائص يجعلني أشعر القذرة).
كنت على حق __toString() للفئة ب يتم استدعاء, لأنه لا يوجد سبب.حتى أن نسميها ، يمكنك استخدام الزهر
class A
{
protected $a;
public function __construct()
{
$this->a = array( (string)new B, (string)new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
ملاحظة:في (سلسلة) قبل أن يلقي الجديد ب ...هذا استدعاء _toString (طريقة) من الفئة B, لكن لن تحصل على ما تريد لأنك سوف تعمل في الكلاسيكية "مزدوجة ترميز" مشاكل بسبب مجموعة المشفرة في الفئة B _toString (طريقة) ، وسوف يتم ترميز مرة أخرى في فئة _toString الأسلوب ().
حتى لا يكون هناك خيار فك النتيجة بعد المدلى بها ، أي:
$this->a = array( json_decode((string)new B), json_decode((string)new B) );
أو أنك سوف تحتاج إلى الحصول على مجموعة ، من خلال خلق toArray (طريقة) في الفئة B أن ترجع مباشرة مجموعة.والتي سوف تضيف بعض التعليمات البرمجية إلى خط أعلاه لأنه لا يمكنك استخدام PHP منشئ مباشرة (لا يمكنك جديد ب()->toArray () ؛ ) لذلك يمكن أن يكون شيئا مثل:
$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );