I have big Object with protected properties and a property can be an array of other Objects. My goal is to print this entire Object as a single nested array. So I need to convert the object to an array.
I’ve tried doing:
$result = (array) $object;
But this converts only the highest lever object to an array and it messes up my protected properties names with weird question mark signs.
I’ve also tried something like this but this simply returns an empty array:
$result= json_decode(json_encode($object), true);
Here is what my object looks like:
object(HandlingModelSearchBookingBooking)[133] protected 'jabooknr' => string '018024709' (length=9) protected 'jitsbooknr' => string '' (length=9) protected 'status' => string 'Y' (length=1) protected 'platform' => int 4 protected 'agentid' => string '' (length=6) protected 'paymentInfo' => null protected 'transports' => array (size=2) 0 => object(HandlingModelSearchBookingTransport)[145] protected 'depdate' => object(DateTime)[146] public 'date' => string '2016-12-06 00:00:00.000000' (length=26) public 'timezone_type' => int 3 public 'timezone' => string 'UTC' (length=3) protected 'carriercode' => string 'TB' (length=2) protected 'carriernumber' => string '2067' (length=4) protected 'brochure' => string '' (length=6) protected 'pax' => array (size=2) 0 => object(HandlingModelSearchBookingPax)[147] protected 'id' => int 1 protected 'title' => string 'MRS' (length=3) protected 'firstname' => string 'MA' (length=7) protected 'name' => string 'BEN' (length=5) protected 'age' => int 58 protected 'luggage' => int 20 protected 'handLuggage' => null 1 => object(HandlingModelSearchBookingPax)[148] protected 'id' => int 2 protected 'title' => string 'MR' (length=2) protected 'firstname' => string 'P' (length=6) protected 'name' => string 'FT' (length=4) protected 'age' => int 60 protected 'luggage' => int 20 protected 'handLuggage' => null protected 'departureAirport' => string 'BRU' (length=3) protected 'arrivalAirport' => string 'AGP' (length=3) 1 => object(HandlingModelSearchBookingTransport)[149] protected 'depdate' => object(DateTime)[150] public 'date' => string '2016-12-13 00:00:00.000000' (length=26) public 'timezone_type' => int 3 public 'timezone' => string 'UTC' (length=3) protected 'carriercode' => string 'TB' (length=2) protected 'carriernumber' => string '2068' (length=4) protected 'brochure' => string '' (length=6) protected 'pax' => array (size=2) 0 => object(HandlingModelSearchBookingPax)[151] protected 'id' => int 1 protected 'title' => string 'MRS' (length=3) protected 'firstname' => string 'MANE' (length=7) protected 'name' => string 'BN' (length=5) protected 'age' => int 58 protected 'luggage' => int 20 protected 'handLuggage' => null 1 => object(HandlingModelSearchBookingPax)[152] protected 'id' => int 2 protected 'title' => string 'MR' (length=2) protected 'firstname' => string 'PIRE' (length=6) protected 'name' => string 'FYT' (length=4) protected 'age' => int 60 protected 'luggage' => int 20 protected 'handLuggage' => null protected 'departureAirport' => string 'AGP' (length=3) protected 'arrivalAirport' => string 'BRU' (length=3) protected 'extraLuggage' => null
EDIT
I have a method in my class where I “find” the result that looks like this:
public function findBooking() { //here happens a bunch of logic to get the right result var_dump($object); exit; // this is the result that is show above return $object; }
Advertisement
Answer
There are a few issues, that make this difficult.
Property visibility, (private, protected) can cause issues when trying to read them outside of the class, proper. This is expected behavior as that’s the point to not use public.
Classes are different. They are well defined and we know them ahead of time, but they are too diverse to account of all property names, at least not with a lot of wasted effort. Not to mention defining them “hard coding” would bite you later as it would make it difficult to maintain. For example if one of the packages does an update and you have coded the property names in you may have issues if they change them. On top of this given that these properties are not part of the classes Public “API” but instead part of the internals, it would not be unreasonable for them to change.
Properties can contain a mix of data types, including other classes or objects. This can make it challenging to handle.
Classes are part of other packages/frameworks and editing them is not practical, this restricts us to working outside of these classes.
So given these difficulties I would recommend using reflection to access the protected properties. Reflection allows you to inspect the definition of classes (and other stuff).
function jsonSerialize($obj){ return json_encode(toArray($obj)); } function toArray($obj){ $R = new ReflectionObject($obj); $proerties = $R->getProperties(); $data = []; foreach($proerties as $k => $v){ $v->setAccessible(true); $property = $v->getName(); $value = $v->getValue($obj); if(!is_object($value)){ $data[$property] = $value; }else if( is_a($obj,'\DateTime')){ //if its a descendant of Datetime, get a formatted date. // you can add other special case classes in this way $data[$property] = $value->format('Y-m-d H:i:s'); }else{ $data[$property] = toArray($value); //call recursively } } return $data; }
So assume we have these classes
class foo{ private $bar; //private nested object public function __construct(){ $this->bar = new bar(); } } class bar{ private $something = 'hello'; } $obj = new foo; echo jsonSerialize($obj);
See it in a sandbox here
Outputs:
{"bar":{"something":"hello"}}
Also of note is we have a special consideration for the DateTime
class. Instead of getting all the properties of this we just want the date (probably) formatted in some standard way. So by using is_a() (I’m old school) we can tell if the Object $value
has a given class as an ancestor of it. Then we just do our formatting.
There are probably a few special cases like this, so I wanted to mention how to handle them.