I have a base class which uses php magic methods __get and __set to be able to modify private attributes in the extended class. Then I built setter getter functions for the relevant private attributes
(similar to what is found here
http://www.beaconfire-red.com/epic-stuff/better-getters-and-setters-php
)
So my child class will look as follows:
class User extends BaseObject {
public $id = -1;
private $_status = "";
function __construct($members = array()) {
parent::__construct($members);
}
//Setter/Getter
public function status($value = null) {
if($value) {
$this->_status = $value;
} else {
return $this->_status;
}
}
Now when I serialize this object which is a JsonSerialize method in the base class, the serialization will only pick up public attributes from the child class (ie "Id") but it won't pick up the private attributes (ie "_status")
This is the serialization function:
public function jsonSerialize() {
$json = array();
foreach($this as $key => $value) {
$json[$key] = $value;
}
return $json;
}
Is there any way the above method in the base class can identify all Getters in the child class so that they can be included in the serialization?
In other words I want the serialization to include both "id" and "status"
I realize I could get all methods on the class and use some kind of naming convention to identify the getter/setter but i specifically need to keep the getter/setter name the same as the attribute name, ie _status MUST have a getter setter called status()
so is there any other way to identify these specific functions?
Maybe a bit different approach:
public function toArray()
{
$descriptor = new \ReflectionClass(get_class($this));
/** #var \ReflectionMethod[] $methods */
$methods = $descriptor->getMethods(\ReflectionMethod::IS_PUBLIC);
$array = [];
foreach ($methods as $method) {
if (substr_compare('get', $method->getName(), 0, 3) === 0) {
$property = lcfirst(substr($method->getName(), 3));
$value = $method->invoke($this);
$array[$property] = $value;
}
}
return $array;
}
Instead of returning array, you can also serialize it. This approach uses public getter methods on child classes.
Use Reflection and ReflectionProperty::setAccessible:
public function jsonSerialize()
{
$json = array();
$class = new ReflectionClass($this);
foreach ($class->getProperties() as $key => $value) {
$value->setAccessible(true);
$json[$value->getName()] = $value->getValue($this);
}
return $json;
}
This is for to answer to explicit question. But I'm not sure that you should use this design in order to solve your problem.
I am going with a different approach...I think i prefer it over reflection.
But am interested to hear if others agree.
I am going to register each Getter/Setter in the child class based on when its accessed, ie I will store the getter/setter name in an array.
At serialization time I will not only interate over all the public attributes but also over the registered getter/setters.
Here is when I register (store the getter/setter name) in my base class, storing it in an array called _getters.
function __set($name,$value){
if(method_exists($this, $name)){
$this->$name($value);
//save in getters list for serialization
if(!in_array($name,$this->_getters))
array_push($this->_getters,$name);
}
else{
// Getter/Setter not defined so set as property of object
$this->$name = $value;
}
}
And now in my serialization I also retrieve any registered getters and serialize them.
its working well!
public function jsonSerialize() {
$json = array();
//do public properties
foreach($this as $key => $value) {
$json[$key] = $value;
}
//do any getter setters
foreach($this->_getters as $key => $getter) {
$value = $this->$getter;
$json[$getter] = $value;
}
return $json;
}
Any issues with this approach?
Related
I implement ArrayAccess in some of my classes and often, I use almost exactly the same code for the ArrayAccess methods in my class as are in Example #1 on the ArrayAccess docs.
Since the code is the same, it would be nice to write this once as a Trait, and then my classes can just implement ArrayAccess (as now), and use ArrayAccessTrait without needing to duplicate a bunch of ArrayAccess methods.
The only thing preventing this is that I usually don't want my underlying array named $container, but something else more pertinent to the class I'm building.
So my question is, is there a way to "alias" whatever array property name I'm using within my own class with the $container being used in my ArrayAccessTrait ?
The example below has a Foo class that shows what I want; this example works, but I'd like to be able to use a property named something other than $container within the Foo class.
trait ArrayAccessTrait {
private $container = [];
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Foo implements ArrayAccess {
use ArrayAccessTrait;
private $container = [];
public function __construct() {
$this->container = [
"one" => 1,
"two" => 2,
"three" => 3,
];
}
public function hello($msg = 'Hello World') {
echo $msg . "<br/>\n";
}
}
$obj = new Foo;
$obj->hello();
echo "Array accessing element ['two'] = " . $obj['two'];
If you're defining private attributes in a trait, you arguably shouldn't be referring to them directly in the classes that implement it. Instead, just use the methods in the trait. I.e., instead of doing this in the concrete class:
$this->container["one"] = 1;
Do this:
$this["one"] = 1;
Then you don't need to know what the storage attribute is called, and you don't need to write any code around setting/getting it. Note also that you do not need to redefine private $container = []; in your class, as it already comes with the trait.
After reading this question
One idea for my problem was to implement the method in the child classes like:
class Child {
private $childField = "Want to see";
public $pubChildField = "Will see";
public function method()
{
$assoc = []; // a associative array
foreach ($this as $field => $value) {
$assoc[$field] = doStuff($value);
}
return $assoc;
}
}
var_dump((new Child())->jsonSerialize());
But for this I'd have to copycat the code in each child class. For readability, I want to refactor it into an (already existing) parentClass similar to:
abstract class parentClass {
public function method()
{
$assoc = []; // a associative array
foreach ($this as $field => $value) {
$assoc[$field] = doStuff($value);
}
return $assoc;
}
}
class Child extends parentClass {
private $childField;
public $pubChildField;
}
var_dump((new Child())->jsonSerialize());
Both foreach ($this as $k=>$v) and get_object_vars() will get the public fields from the instance. I need to get it's private fields thou, for serialization.
Edit 1: Typo abstract method - what was i thinking
Edit 2: Clarified the example
Edit 3: Reformulated the question since there seemed to be misconception
Abstract class is about abstract methods, not inherited. I assume these
are not relevant to the problem (otherwise make it ordinary default/base class).
private limits visibility to concrete class - use protected
instead.
It seems to me that you need only code reuse. You might reach for traits in this casse - traits becoming part of a class, and you can iterate private fields.
interface ArrayData {
public function toArray(): array;
}
trait ToArray {
public function toArray(): array {
$assoc = []; // a associative array
foreach ($this as $field => $value) {
$assoc[$field] = $value;
}
return $assoc;
}
}
class Foo implements ArrayData {
use ToArray;
private $childField = "Want to see";
public $pubChildField = "Will see";
}
$foo = new Foo();
var_dump($foo->toArray());
The correct solution is to declare the base class implements the JsonSerializable interface and implement the JsonSerializable::jsonSerialize() method in all children classes.
You can let it unimplemented in the base class to force all the children classes implement it or you can provide a default implementation (return array();) in the base class for the children classes that do not need to be serialized.
The implementation in each class should not iterate over the list of object properties. Usually, not all object properties must be serialized and the serializable properties are not born equal.
abstract class parentClass implements JsonSerializable {
// You can remove this default implementation
// to force all children class implement the method
public function jsonSerialize()
{
return array();
}
}
class childClass extends parentClass {
private $childField;
public $pubChildField;
public function jsonSerialize()
{
return array(
'field1' => $this->childField,
'field2' => base64_encode($pubChildField),
// etc ...
);
}
}
var_dump((new Child())->jsonSerialize());
Try to write code that is easier to read and understand. You will thank yourself later.
I have the following class tree:
class A /* Base class */
{
private/protected/public $state
}
class B extends A /* Auto generated class, not to be modified */
{
private $v
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
There is only one class A. There are multiple classes like class B, and all of those classes will have a subclass like C. Class B gets auto-generated and should not be modified.
I am storing objects of type(s) C in the session. What I want to do is to store some state information in every instance, just before PHP gets it serialised, and that will do something with it when it's unserialised. I want all this to be implemented in class A.
Considering, I need to use either __sleep() or Serializable interface. Using __sleep is out of the question, because of what the PHP manual says:
It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Instead you may use the Serializable interface.
Meaning that if I sleep an instance of class C, I'll loose the private variables declared in B. So I want to use Serializable, but for some reason, I simply can't get it to do what I want.
In essence, I would like the object to be serialised just as if I didn't implement any serialisation stuff myself, I just want to add information to $state right before it happens. I've tried covering all data with ReflectionObject->getProperties(), but I can't seem to find the right way to fetch and set the private values in class B to be serialised and unserialised.
How do I do this?
You can do this using the Reflection classes. You'll have to get the properties of the class itself and each of it's parent classes. Getting and setting the property values can be done using ReflectionProperty's getValue and setValue methods, combined with setAccessible to get access to private and protected properties. Combining those, I came up with the following code:
<?php
class A implements Serializable /* Base class */
{
protected $state;
public function serialize()
{
$this->state = "something";
return serialize($this->_getState());
}
public function unserialize($data)
{
$this->_setState(unserialize($data));
}
protected function _getState()
{
$reflClass = new ReflectionClass(get_class($this));
$values = array();
while ($reflClass != null)
{
foreach ($reflClass->getProperties() as $property)
{
if ($property->getDeclaringClass() == $reflClass)
{
$property->setAccessible(true);
$values[] = array($reflClass->getName(), $property->getName(), $property->getValue($this));
}
}
$reflClass = $reflClass->getParentClass();
}
return $values;
}
protected function _setState($values)
{
foreach ($values as $_)
{
list($className, $propertyName, $propertyValue) = $_;
$property = new ReflectionProperty($className, $propertyName);
$property->setAccessible(true);
$property->setValue($this, $propertyValue);
}
}
}
class B extends A /* Auto generated class, not to be modified */
{
private $v;
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
$instance = new C();
$instance->setV("value");
$s = serialize($instance);
$instance2 = unserialize($s);
var_dump($instance, $instance2);
Which seems to do what you want.
This question already has answers here:
Magic __get getter for static properties in PHP
(6 answers)
Closed 9 years ago.
I'm trying to convert array to object. I want to use magic methods - __get and __set with static properties.
My code:
class UserData {
private static $id, $name, $login;
public function __get($var)
{
return self::$var;
}
public function __set($var, $val)
{
self::{$var} = $val;
}
}
And setting:
foreach($userArray as $key => $val)
{
DaneBilingowe::${$key} = $val;
}
Error:
Fatal error: Cannot access private property UserData::$id
Is it possible to use magic method with static property?
In short, no.
__get() and __set() are instance methods. They are essentially the functions that make up the stdClass(), which is an instance.
If you must set static content in this manner you can give the class a stdClass parameter and a singleton structure that would allow you to magically set and get data.
For example:
class UserData {
protected static $_instance;
protected $_data = array();
public static function get_instance() {
static $initialized = FALSE;
if ( ! $initialized) {
self::$_instance = new UserData;
$initialized = TRUE;
}
return self::$_instance;
}
public function __get($var) {
$self = self::get_instance();
return isset($self->_data[$var]) ? $self->_data[$var] : NULL;
}
public function __set($var, $val) {
$self = self::get_instance();
$self->_data[$var] = $val;
}
}
Then you could go:
$UserData =& UserData::get_instance();
$UserData->var = 'val';
echo $UserData->var; // prints 'val'
I don't recommend using Singletons in PHP, however, because they are pointless. You can read some reasons why in the post Best practice on PHP singleton classes.
Either use a static class or an instance class.
Magic getters and setters are shortcuts. You can implement the same behavior with normal setters and getters. The example below provides the same functionality, but the intent is a lot more clear:
class UserData {
protected $id, $name, $login;
public static function set_name($name) {
self::$name = $name;
}
public static function set_login($login) {
self::$login = $login;
}
public static function get_id() {
return self::$id;
}
public static function get_name() {
return self::$name;
}
public static function get_login() {
return self::login;
}
}
Notice how in the above code $id is not writable. It is only readable. $name and $login are readable and writable. It is easier and less buggy to control reading and writing using normal setters and getters. Magic methods are just that, magic, and usually magic is not concrete and is less understandable in code.
The final point I want to make is, why would UserData be static? Unless you only have 1 user in the entirety of your code it doesn't make sense to have it static. Perhaps I am not getting the whole picture, but something with an id and name should be instantiated so that you can have multiple instances. Otherwise, why have the id because the class itself is unique.
If you really want to use magic methods on static properties, you can but you will need an instance. Though it does not look reasonable, being a programmer itself is not reasonable at all :)
Also user defined classes and objects are not dynamic in php.
You can not add variables to them that easily... So you can use the pattern below:
class UserData {
private static $id, $name, $login, $arr = [];
public function __get($var){
return (array_key_exists(self::$arr, $var)? self::$arr[$var]:null;
}
public function __set($var, $val){
self::$arr[$var] = $val;
}
}
And setting: Well what is DaneBilingowe? I do not now here... But:
$inst = new UserData();
foreach($userArray as $key => $val){
$inst->$key = $val;
}
will work.
But beware, It will work only on class (static) memory.
Also since there is no appropriate filtering for setting names, weird things can happen.
(That means you should add them)
I am creating a __get() function for a class to control access to my private member variables. Do I need to design the function to handle all possible member value reads or can I not write it for members that are public? Also, I am assuming that classes that inherit this class will use my __get() function to access private members.
class ClassA{
private $collection = array();
public $value;
function __get($item){
return $collection[$item];
}
No, you don't.
class A {
public $foo = 'bar';
private $private = array();
public function __get($key) {
echo 'Called __get() at line #' ,__LINE__, ' with key {', $key ,'}',"\n";
return $this->private[$key];
}
public function __set($key, $val) {
$this->private[$key] = $val;
}
}
$a = new A();
var_dump($a->foo);
$a->bar = 'baz';
var_dump($a->bar);
And yes, it will:
class B extends A { private $private = array(); }
$b = new B();
var_dump($b->bar);
Well, your code would fail on private items not set in your array. But then again, you can use this as a way to deal with what's in and out of your array, as such ;
function __get($item){
if ( isset ( $collection[$item] ) )
return $collection[$item];
else {
try {
return $this->$item ; // Dynamically try public values
} catch (Exception $e) {
$collection[$item] = 0 ; // Make it exist
}
}
}
Classes that inherit your calls will use this __get(), but can be overridden, so use parent::__construct() for explicity. Also note that these cannot be static. Further reading.
First of all PHP searches for property name in class definition and tries to return its value. If there's no property - PHP tries to call __get($var) and here you can return anything you want. This is a little confusing behavior for those, who know Java-like getters/setters where you have to define them for every class member you want to access.
When it's comfortable to use Java-like getters/setters - you may write something like this:
public function __set($var, $value)
{
if (method_exists($this, $method = "_set_" . $var))
{
call_user_func(array($this, $method), $value);
}
}
public function __get($var)
{
if (method_exists($this, $method = "_get_" . $var))
{
return call_user_func(array($this, $method), $value);
}
}
and then use this code by defining custom getters/setters
protected function _get_myValue()
{
return $this->_myValue;
}
protected function _set_myValue($value)
{
$this->_myValue = $value;
}
and access to defined methods this way:
$obj->myValue = 'Hello world!';