Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP: __toString() and json_encode() not playing well together

I've run into an odd problem and I'm not sure how to fix it. I have several classes that are all PHP implementations of JSON objects. Here' an illustration of the issue

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;

The output from this is

[{},{}]

When the desired output is

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

The problem is that I was relying on the __toString() hook to do my work for me. But it can't, because the serialize that json_encode() uses won't call __toString(). When it encounters a nested object it simply serializes public properties only.

So, the question then become this: Is there a way I can develop a managed interface to JSON classes that both lets me use setters and getters for properties, but also allows me to get the JSON serialization behavior I desire?

If that's not clear, here's an example of an implementation that won't work, since the __set() hook is only called for the initial assignment

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;

I suppose I could also do something like this

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;

But then I would have to rely on the diligence of the other developers to use the setters - I can't force adherence programmtically with this solution.

---> EDIT <---

Now with a test of Rob Elsner's response

<?php

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

    public function getIterator()
    {
        echo __METHOD__;
    }
}

echo json_encode( new a );

When you execute this, you can see that the getIterator() method isn't ever invoked.

like image 639
Peter Bailey Avatar asked Dec 31 '08 00:12

Peter Bailey


People also ask

What does the PHP function json_encode () do?

The json_encode() function is used to encode a value to JSON format.

How can I get JSON encoded data in PHP?

To receive JSON string we can use the “php://input” along with the function file_get_contents() which helps us receive JSON data as a file and read it into a string. Later, we can use the json_decode() function to decode the JSON string.

What does json_encode return?

Syntax. The json_encode() function can return a string containing the JSON representation of supplied value. The encoding is affected by supplied options, and additionally, the encoding of float values depends on the value of serialize_precision.

What is json_encode and Json_decode in PHP?

JSON data structures are very similar to PHP arrays. PHP has built-in functions to encode and decode JSON data. These functions are json_encode() and json_decode() , respectively. Both functions only works with UTF-8 encoded string data.


1 Answers

A late answers but might be useful for others with the same problem.

In PHP < 5.4.0 json_encode doesn't call any method from the object. That is valid for getIterator, __serialize, etc...

In PHP > v5.4.0, however, a new interface was introduced, called JsonSerializable.

It basically controls the behaviour of the object when json_encode is called on that object.


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"}]"

like image 104
Tivie Avatar answered Sep 18 '22 05:09

Tivie