3

I am using PHP 5.2.x and want to encode objects of my custom PHP classes with only private members. One of the private members is an array of objects of another custom class.

I tried the solution outlined in https://stackoverflow.com/a/7005915/17716, but that obviously does not work recursively. The only solution that I can see is extending the json_encode method somehow ,so that the class' version of the json_encode method is called instead of the default method.

For reference, the code is as follows:

Class A {
    private $x;
    private $y;
    private $z;
    private $xy;
    private $yz;
    private $zx;

    public function f1() {
        ...
    }

    public function f2() {
        ...
    }
    .
    .
    .
    public function getJSONEncode() {
         return json_encode(get_object_vars($this));
    }
}

class B {

    private $p; //This finally stores objects of class A
    private $q;
    private $r;

    public function __construct() {
            $this->p = array();
    }

    public function fn1() {
        ...
    }

    public function fn2() {
        ...
    }

    .
    .
    .

    public function getJSONEncode() {
         return json_encode(get_object_vars($this));
    }

}

class C {

    private $arr;

    public function __construct() {
            $this->arr = array();
    }

    public function fillData($data) {
        $obj = new B();
        //create objects of class B and fill in values for all variables
        array_push($this->arr, $obj)
    }

    public function returnData() {
          echo $this->arr[0]->getJSONEncode(); //Edited to add

    }

}

What would be the best way to achieve this nested json encoding?

Edited to Add:

The output I get when the returnData method is executed is:

{"p":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"q":"Lorem Ipsum","r":"Dolor Sit Amet"}
11
  • 1
    I don't see any Nested json here .. Class A { class PostAttributes { is invalid .. Am not sure i totally get what you want Commented Oct 8, 2012 at 17:14
  • Apologies and thanks for catching that. The "Class PostAttributes {" was a copy paste error. Also added the comment that the array P contains objects of Class A Commented Oct 8, 2012 at 17:21
  • what exactly are you executing .. its not in the above code Commented Oct 8, 2012 at 17:23
  • Your code formatting is a little off there. Commented Oct 8, 2012 at 17:24
  • @Baba: Not sure I understand your question... Can you elaborate? Commented Oct 8, 2012 at 17:33

1 Answer 1

2

Whilst I believe you would be better off writing a proper export/encode function for each of your classes (which would construct a public object from both private and public values just for encoding - you could be quite clever and use php's reflection ability) - instead - you could make use of the following code:

/// example class B
class TestB {
  private $value = 123;
}

/// example class A
class TestA {
  private $prop;
  public function __construct(){
    $this->prop = new TestB();
  }
}

function json_encode_private($obj){
  /// export the variable to find the privates
  $exp = var_export($obj, true);
  /// get rid of the __set_state that only works 5.1+
  $exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
  /// rebuild the object
  eval('$enc = json_encode('.$exp.');');
  /// return the encoded value
  return $enc;
}

echo json_encode_private(new TestA());

/// {"prop":{"value":123}}

So the above should work, but I wouldn't recommend using eval anywhere in php - just because I always hear alarm bells quietly off in the distance :)

update

Just had a thought of what might make this a little safer, rather than using eval you could use create_function which would limit some of its creational powers, or at least the scope of those powers...

function json_encode_private($obj){
  $exp = var_export($obj, true);
  $exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
  $enc = create_function('','return json_encode('.$exp.');');
  return $enc();
}

update 2

Had a chance to play around with another way of converting an object with private properties, to an object with public properties - using only a simple function (and no eval). The following would need to be tested on whichever version of PHP you are using as it's behaviour - again - might not be reliable.... due to the weird \0Class Name\0 prefixing in the converted private properties (see comments in code).

For more info on this strange prefixing behaviour:
https://www.php.net/language.types.array.php#language.types.array.casting

Anyway, so using a test class:

class RandomClass {
  private $var = 123;
  private $obj;
  public function __construct(){
    $this->obj = (object) null;
    $this->obj->time = time();
  }
}

We can use the following function to convert it to a public object:

function private_to_public( $a ){
  /// grab our class, convert our object to array, build our return obj
  $c = get_class( $a ); $b = (array) $a; $d = (object) null;
  /// step each property in the array and move over to the object
  /// usually this would be as simple as casting to an object, however
  /// my version of php (5.3) seems to do strange things to private 
  /// properties when casting to an array... hence the code below:
  foreach( $b as $k => $v ){
    /// for some reason private methods are prefixed with a \0 character
    /// and then the classname, followed by \0 before the actual key value. 
    /// This must be some kind of internal protection causing private  
    /// properties to be ignored. \0 is used by some languges to terminate  
    /// strings (not php though, as far as i'm aware).
    if ( ord($k{0}) === 0 ) {
      /// trim off the prefixed weirdnesss..?!
      $e = substr($k, 1 + strlen($c) + 1);
      /// unset the $k var first because it will remember the \0 even 
      /// if other values are assigned to it later on....?!
      unset($k); $k = $e;
    }
    /// so if we have a key, either public or private - set our value on
    /// the destination object.
    if ( $k !== '' && $k !== NULL && $k !== FALSE )  {
      $d->{$k} = $v;
    }
  }
  return $d;
}

So if we put it all together:

$a = new RandomClass();

echo json_encode( private_to_public( $a ) );

/// {"var":123,"obj":{"time":1349777323}}

Again your best / most reliable bet is to either bespokely code your conversion methods for each class, or create some kind of generalised solution using Class Reflection, but that latter is far more involved, more involved than a StackOverflow answer... at least with the amount of time I have free ;)

further info

The above code will work when trying to access objects from anywhere, the reason for implementing this was because my first attempt was obviously to use the following:

echo json_encode( get_object_vars($a) );

/// you will get {} which isn't what you expect

It seems that if you want to use get_object_vars you have to use it from a context that has access to all the properties, i.e. from inside the class you are exposing:

public function getAllProperties(){
  return get_object_vars( $this );
}

So, imagine we'd added the above to the RandomClass definition:

echo json_encode( $a->getAllProperties() );

/// you will get {"var":123,"obj":{"time":1349777323}}

This works because the members of a class have access to all the class's properties public or private.... so as I say, working this way is far far superior; superior, but not always possible.

Sign up to request clarification or add additional context in comments.

4 Comments

Sorry, I'm not entirely familiar with out OO works in PHP, but isn't there anyway that I can extend the json_encode method (or my classes) so that when encoding is tried, they automatically get called? - Something similar to what JSONSerialize makes possible but in PHP 5.4 and above
No, not that I know of, it's thre reason why JSONSerialize was created I guess. There are the magic functions __sleep and __wakeup but they only work for serialize and unserialize. And there is the magic function __set_state but that is for use with var_export. php.net/manual/en/language.oop5.magic.php - just found this other SO question along the same lines too stackoverflow.com/questions/8778020/… (no non-hack answers there either)
Thank you for the variety of options to workaround this. I'm also going to try and push my hosting provider to upgrade their version of PHP so that I can use the simpler JSONSerialize interface. Until them, you're answer is the way to go :)
@AJ. No problem... your question forced me to learn something new about get_object_vars so all is good! Yep asking them to upgrade sounds like a plan :) only possible problem to that is php 5.4 was released 2012-03-01 and most hosts that I know of would only ever consider upgrading after a release had been in the wild for a least a year - if not more. If you can get a dedicated server you can do what you like though, although that would work out as rather an expensive json_encode function ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.