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
Related
I am wondering how PHP determines the equality of instances of a class with private properties:
class Example {
private $x;
public $y;
public __construct($x,$y) {
$this->x = $x; $this->y = $y;
}
}
and something like
$needle = new Example(1,2);
$haystack = [new Example(2,2), new Example(1,2)];
$index = array_search($needle, $haystack); // result is 1
The result is indeed 1, so the private member is compared. Is there a possibility to only match public properties?
I know I could overwrite the __toString method and cast all arrays and needles to string, but that leads to ugly code.
I am hoping to find a solution that is elegant enough to work with in_array, array_search, array_unique, etc.
A possible solution could be the PHP Reflection API. With that in mind you can read the public properties of a class and compare them to other public properties of another instance of the same class.
The following code is a simple comparison of public class properties. The base for the comparison is a simple value object.
declare(strict_types=1);
namespace Marcel\Test;
use ReflectionClass;
use ReflectionProperty;
class Example
{
private string $propertyA;
public string $propertyB;
public string $propertyC;
public function getPropertyA(): string
{
return $this->propertyA;
}
public function setPropertyA(string $propertyA): self
{
$this->propertyA = $propertyA;
return $this;
}
public function getPropertyB(): string
{
return $this->propertyB;
}
public function setPropertyB($propertyB): self
{
$this->propertyB = $propertyB;
return $this;
}
public function getPropertyC(): string
{
return $this->propertyC;
}
public function setPropertyC($propertyC): self
{
$this->propertyC = $propertyC;
return $this;
}
public function __compare(Example $b, $filter = ReflectionProperty::IS_PUBLIC): bool
{
$reflection = new ReflectionClass($b);
$properties = $reflection->getProperties($filter);
$same = true;
foreach ($properties as $property) {
if (!property_exists($this, $property->getName())) {
$same = false;
}
if ($this->{$property->getName()} !== $property->getValue($b)) {
$same = false;
}
}
return $same;
}
}
The __compare method of the Example class uses the PHP Reflection API. First we build a reflection instance of the class to which we want to compare to the current instance. Then we request all public properties of the class we want to compare to. If a public property does not exist in the instance or the value of the property is not the same as in the object we want to compare to, the method returns false, otherwise true.
Some examples.
$objectA = (new Example())
->setPropertyA('bla')
->setPropertyB('yadda')
->setPropertyC('bar');
$objectB = (new Example())
->setPropertyA('foo')
->setPropertyB('yadda')
->setPropertyC('bar');
$result = $objectA->__compare($objectB);
var_dump($result); // true
In this example the comparison results into true because the public properties PropertyB and PropertyC exist in both instances and have the same values. Keep in mind, that this comparison works only, if the second instance is the same class. One could spin this solution further and compare all possible objects based on their characteristics.
In Array Filter Example
It is a kind of rebuild of the in_array function based on the shown __compare method.
declare(strict_types=1);
namespace Marcel\Test;
class InArrayFilter
{
protected ArrayObject $data;
public function __construct(ArrayObject $data)
{
$this->data = $data;
}
public function contains(object $b)
{
foreach ($this->data as $object) {
if ($b->__compare($object)) {
return true;
}
}
return false;
}
}
This filter class acts like the in_array function. It takes a collection of objects and checks, if an object with the same public properties is in the collection.
Conclusion
If you want this solution to act like array_unique, array_search or ìn_array you have to code your own callback functions which execute the __compare method in the way you want to get the result.
It depends on the amount of data to be handled and the performance of the callback methods. The application could consume much more memory and therefore become slower.
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
I've got an event object that looks like this:
class InputEvent
{
protected $input;
public function __construct(&$input)
{
$this->input = &$input;
}
public function getInput()
{
return $this->input;
}
}
Now, since $input is a string, I have to pass it in by reference. However, when I create an event listener, input value doesn't get changed since getInput function returns $input value rather than reference to $input property.
class Listener
{
public function myEvent(InputEvent $event)
{
$input = $event->getInput();
// doesn't change InputEvent::input property
$input = "asd";
}
}
Is there a way for me to return reference to $input value, so that it gets changed inside event object?
I can make two workarounds, but both are.....well, workarounds, not really pretty.
Wrap $input into a class, for example creating a String class which would only hold value and getter, setter methods.
Add setInput method to InputEvent, but still isn't as elegant as just assigning it to a value like in Listener example above.
As moonwave99 mentioned above, you'll need to use a setter. In your InputEvent class add the method:
public function setInput(&$s) {
$this->input = $s;
}
Or you could return a reference:
public function &getInputRef() {
return $this->input;
}
$input = &$event->getInputRef();
Which I think should work the same.
Have your getInput function return by reference
function &getInput()
^-- signifies return value by reference
class InputEvent
{
protected $input;
public function __construct(&$input)
{
$this->input = $input;
}
public function &getInput()
{
return $this->input;
}
}
$string = "abc";
$a = new InputEvent($string);
$input = &$a->getInput();
$input .= "def";
echo $a->getInput();
I have a class which contains an array of objects and has methods to return an object from that array by reference. There is also a method to unset an object from the array.
However if I have a variable that references an object from the array, and that element is unset, the variable still has a reference to it. What do I need to do in the remove method that will destroy that object for good, including references to it.
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new myObject();
}
public function &getObjectByReference()
{
return $this->objectList[0];
}
public function removeObject()
{
unset($this->objectList[0]);
}
}
$myClass = new myClass();
$referencedObject = $myClass->getObjectByReference();
// this can now use the methods from myObject
$myClass-> removeObject();
// supposed to delete the object from memory. However $referencedObject is still
// able to use all the methods from myObject.
So thats the problem I am having, I need to be able to remove from the array and delete the object from memory so variables that reference that object are no longer usable.
Have you tried doing:
$referencedObject = &$myClass->getObjectByReference();
Is $referencedObject still there after putting in that &?
To create a reference from a return value both the function must return by reference (you already do so) and the assignment has to by by reference. So you should write:
$referencedObject =& $myClass->getObjectByReference();
If you do this the reference really will be destroyed.
But if you want to do destroy all variables having this object as value (and which are not references) then this is impossible. You can only remove the real references, not variables having the same value ;)
Php is working with garbage collector : if there is still a reference to the object then the object is not deleted.
unset($this->objectList[0])
Does not delete the object but the value in $this->objectList, the object still exists since he is referenced by $referencedObject.
One solution : when you delete the object, tell him he is being deleted and in that object you have a boolean "isDeleted". Then for every method of that object, check if isDeleted is true and in that case, just do nothing.
This is the nature of PHP's garbage collector. To make sure a caller doesn't maintain a reference to your object you have to ensure they can never touch the original object. Here is an idea:
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new Wrapper(new myObject());
}
public function getObject()
{
return $this->objectList[0];
}
public function removeObject()
{
$this->objectList[0]->emptyWrapper();
unset($this->objectList[0]);
}
}
class Wrapper {
private $object;
public function __construct($object) {
$this->object = $object;
}
public function __call($method, $args) {
return call_user_func_array(array($this->object, $method), $args);
}
public function __get($attr) {
return $this->object->$attr;
}
public function __set($attr, $value) {
$this->object->$attr = $value;
}
public function emptyWrapper() {
$this->object = null;
}
}
You can use this wrapper idea or you can use forced indirection and handles. I might prefer using forced indirection instead; otherwise, a caller can still keep the wrapper object alive - though it is fairly cheap.
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new myObject();
}
public function getObjectHandle() {
return 0;
}
public function removeObject($h)
{
unset($this->objectList[$h]);
}
public function call($h, $method, $args) {
call_user_func(array($this->objectList[$h], $method), $args);
}
public function get($h, $attr) {
return $this->objectList[$h]->$attr;
}
public function set($h, $attr, $value) {
$this->objectList[$h]->$attr = $value;
}
}
$myClass = new myClass();
$objectHandle = $myClass->getObjectHandle();
// example method call
$myClass->call($objectHandle, 'some_method', array('arg1', 'arg2'));
$myClass->removeObject($objectHandle);
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);