Cast a class instance (object) to associative array creates strange keys [duplicate] - php

I'm trying to JSON encode some objects in PHP, but I'm facing a problem: I want to encode data which is kept by a class private members.
I found this piece of code to encode this object by calling an encode function like:
public function encodeJSON()
{
foreach ($this as $key => $value)
{
$json->$key = $value;
}
return json_encode($json);
}
However, this only works if the object I want to encode does not contain other objects inside, which is the case. How can I do to encode not only the "outer" object, but encode as well any members that are objects too?

The best method to serialize an object with private properties is to implement the \JsonSerializable interface and then implement your own JsonSerialize method to return the data you require to be serialized.
<?php
class Item implements \JsonSerializable
{
private $var;
private $var1;
private $var2;
public function __construct()
{
// ...
}
public function jsonSerialize()
{
$vars = get_object_vars($this);
return $vars;
}
}
json_encode will now serialize your object correctly.

If you're using php 5.4 you can use the JsonSerializable interface: http://www.php.net/manual/en/class.jsonserializable.php
You just implement a jsonSerialize method in your class which returns whatever you want to be encoded.
Then when you pass your object into json_encode, it'll encode the result of jsonSerialize.

Anyway. You need create public method in your class to return all their fields json encoded
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}

I think #Petah's got the best approach, but that way you lose properties that are array or object. So I added a function wich do that recursively:
function json_encode_private($object) {
function extract_props($object) {
$public = [];
$reflection = new ReflectionClass(get_class($object));
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$value = $property->getValue($object);
$name = $property->getName();
if(is_array($value)) {
$public[$name] = [];
foreach ($value as $item) {
if (is_object($item)) {
$itemArray = extract_props($item);
$public[$name][] = $itemArray;
} else {
$public[$name][] = $item;
}
}
} else if(is_object($value)) {
$public[$name] = extract_props($value);
} else $public[$name] = $value;
}
return $public;
}
return json_encode(extract_props($object));
}
EDIT: Added is_object() check inside the array loop to avoid a get_class() exception in the next extract_props() call when the array elements are not objects, like strings or numbers.

I think this may be a great case for the Usage of Traits
using the below guist I implemented jsonSerializable interface in multiple points of my app while keeping the code manageable
https://gist.github.com/zburgermeiszter/7dc5e65b06bb34a325a0363726fd8e14
trait JsonSerializeTrait
{
function jsonSerialize()
{
$reflect = new \ReflectionClass($this);
$props = $reflect->getProperties(\ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE);
$propsIterator = function() use ($props) {
foreach ($props as $prop) {
yield $prop->getName() => $this->{$prop->getName()};
}
};
return iterator_to_array($propsIterator());
}
}
then you just have to do
class YourClass implements JsonSerializable
{
use JsonSerializeTrait;
... normal encapsulated code...
}

public function jsonSerialize()
{
$objectArray = [];
foreach($this as $key => $value) {
$objectArray[$key] = $value;
}
return json_encode($objectArray);
}
I personally think this is a way of doing it. It is similar to Petah's, except It keeps in line with encapsulation well, because the array is populated from the object.
Put this function in either your object or as a trait to be used by your object. To each their own though.

This would print a JSON with all of the properties (public, private and protected) of class foo:
$reflection = new ReflectionClass('Foo');
$properties = $reflection->getdefaultProperties();
echo json_encode($properties);
It would work from any context.

You can only encode an object's private members from within the class. As a side note though, does the json_enocde function not work for you? http://php.net/manual/en/function.json-encode.php

Using reflection you can json_encode private properties, although its not considered best practice:
function json_encode_private($object) {
$public = [];
$reflection = new ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$public[$property->getName()] = $property->getValue($object);
}
return json_encode($public);
}
E.g.
class Foo {
public $a = 1;
public $b = 2;
}
class Bar {
private $c = 3;
private $d = 4;
}
var_dump(json_encode(new Foo()));
var_dump(json_encode_private(new Bar()));
Outputs:
string(13) "{"a":1,"b":2}"
string(13) "{"c":3,"d":4}"
http://codepad.viper-7.com/nCcKYW

Related

Is it possible to make a class' variable read-only in PHP? [duplicate]

Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);

A faster way of doing objectToArray

Ive got this snippet of code below which works perfectly fine. I have been profiling it and the bit of code gets used alot of times, so I want to try figure out how to write it in a way that will perform better than the current way its written.
Is there a more efficient way to write this?
function objectToArray($d) {
if (is_object($d)) {
// Gets the properties of the given object
// with get_object_vars function
$d = get_object_vars($d);
}
if (is_array($d)) {
// Return array converted to object Using __FUNCTION__ (Magic constant) for recursive call
return array_map(__FUNCTION__, $d);
}
else {
// Return array
return $d;
}
}
You could implement a toArray() method to the class that needs to be converted:
e.g.
class foo
{
protected $property1;
protected $property2;
public function __toArray()
{
return array(
'property1' => $this->property1,
'property2' => $this->property2
);
}
}
Having access to the protected properties and having the whole conversion encapsulated in the class is in my opinion the best way.
Update
One thing to note is that the get_object_vars() function will only return the publically accessible properties - Probably not what you are after.
If the above is too manual of a task the accurate way from outside the class would be to use PHP (SPL) built in ReflectionClass:
$values = array();
$reflectionClass = new \ReflectionClass($object);
foreach($reflectionClass->getProperties() as $property) {
$values[$property->getName()] = $property->getValue($object);
}
var_dump($values);
depends what kind of object it is, many standard php objects have methods built in to convert them
for example MySQLi results can be converted like this
$resultArray = $result->fetch_array(MYSQLI_ASSOC);
if its a custom class object you might consider implementing a method in that class for that purpose as AlexP sugested
Ended up going with:
function objectToArray($d) {
$d = (object) $d;
return $d;
}
function arrayToObject($d) {
$d = (array) $d;
return $d;
}
As AlexP said you can implement a method __toArray(). Alternatively to ReflexionClass (which is complex and expensive), making use of object iteration properties, you can iterate $this as follow
class Foo
{
protected $var1;
protected $var2;
public function __toArray()
{
$result = array();
foreach ($this as $key => $value) {
$result[$key] = $value;
}
return $result;
}
}
This will also iterate object attributes not defined in the class: E.g.
$foo = new Foo;
$foo->var3 = 'asdf';
var_dump($foo->__toArray());)
See example http://3v4l.org/OnVkf
This is the fastest way I have found to convert object to array. Works with Capsule as well.
function objectToArray ($object) {
return json_decode(json_encode($object, JSON_FORCE_OBJECT), true);
}

How to control json_encode behavior?

Is there any way to control json_encode behavior on objects? Like excluding empty arrays, null fields and so on?
I mean something like when using serialize(), where you can implement magic __sleep() method and specify what properties should be serialized:
class MyClass
{
public $yes = "I should be encoded/serialized!";
public $empty = array(); // // Do not encode me!
public $null = null; // Do not encode me!
public function __sleep() { return array('yes'); }
}
$obj = new MyClass();
var_dump(json_encode($obj));
The most correct solution is extending the interface JsonSerializable;
by using this interface you just need to return with the function jsonSerialize() what you want json_encode to encode instead of your class.
Using your example:
class MyClass implements JsonSerializable{
public $yes = "I should be encoded/serialized!";
public $empty = array(); // // Do not encode me!
public $null = null; // Do not encode me!
function jsonSerialize() {
return Array('yes'=>$this->yes);// Encode this array instead of the current element
}
public function __sleep() { return array('yes'); }//this works with serialize()
}
$obj = new MyClass();
echo json_encode($obj); //This should return {yes:"I should be encoded/serialized!"}
Note: this works in php >= 5.4 http://php.net/manual/en/class.jsonserializable.php
You could make the variables private. Then they won't show up in the JSON encoding.
If that is not an option, you could make a private array, and use the magic methods __get($key) and __set($key,$value) to set and get values in/from this array. In your case the keys would be 'empty' and 'null'. You can then still access them publicly but the JSON encoder will not find them.
class A
{
public $yes = "...";
private $privateVars = array();
public function __get($key)
{
if (array_key_exists($key, $this->privateVars))
return $this->privateVars[$key];
return null;
}
public function __set($key, $value)
{
$this->privateVars[$key] = $value;
}
}
http://www.php.net/manual/en/language.oop5.overloading.php#object.get

PHP Reference Objects in Foreach Loop

Lets say I have these classes:
class Foo {
public $_data;
public function addObject($obj) {
$this->_data['objects'][] = $obj;
}
}
class Bar {
public $_data;
public function __construct() {
$this->_data['value'] = 42;
}
public function setValue($value) {
$this->_data['value'] = $value;
}
}
$foo = new Foo();
$bar = new Bar();
$foo->addObject($bar);
foreach($foo->_data['objects'] as $object) {
$object->setValue(1);
}
echo $foo->_data['objects'][0]->_data['value']; //42
My actual code is this, very similar, uses ArrayAccess:
foreach($this->_data['columns'] as &$column) {
$filters = &$column->getFilters();
foreach($filters as &$filter) {
$filter->filterCollection($this->_data['collection']);
}
}
filterCollection changes a value in $filter, but when you look at the $this object, the value is not right.
foreach($foo->_data['objects'] as &$object) {
$object->setValue(1);
}
Notice the &
Foreach operates on a copy of the array. Use an & before the object variable.
foreach($foo->_data['objects'] as &$object)
PHP paradigm is that objects (and resources) are always references, while other types (base types or arrays) are copied, so the & operator has no effect on objects (and is meaningless on resources since only "special functions" i.e. external library modules can take them as parameters), but allows to pass variables of other types by reference.

read-only properties in PHP?

Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);

Categories