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.
Related
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'm a bit confused on whether or not this is possible. I've checked a couple of posts here on SO and they don't really explain what I'm looking for.
I have 3 classes. One main class and two classes extending that main class. (see code below). Is it possible to run a method in one of the two extended classes from it's sibling (the other extended class)?
If it's not possible, how can I change my code to accomplish what I'm doing in the example below?
DECLARATION
class A {
public function __construct() {
//do stuff
}
}
class B extends A {
private $classb = array();
public function __construct() {
parent::__construct();
//do stuff
}
public function get($i) {
return $this->classb[$i];
}
public function set($i, $v) {
$this->classb[$i] = $v;
}
}
class C extends A {
public function __construct() {
parent::__construct();
//do stuff
}
public function display_stuff($i) {
echo $this->get($i); //doesn't work
echo parent::get($i); //doesn't work
}
}
USAGE
$b = new B();
$c = new C();
$b->set('stuff', 'somestufftodisplay');
$c->display_stuff('stuff'); // <----- Displays nothing.
Your code shows an additional problem apart from the main question so there are really two answers:
No, you cannot run a method from a sibling class in another sibling class. If you need that, the method should be in the parent class. The same applies to properties.
You cannot use the value of a property from one object in another object, even if they are both of the same class. Setting a property value in one object sets its value only there as different objects can have the same properties with completely different values. If you need to share the value of a property between the objects and also be able to modify it, you should use a static property. In this case you would have to define that in the parent class, see my previous point.
So to make it work, you would need something like
class A {
private static $var = array();
public function get($i) {
return self::$var[$i];
}
public function set($i, $v) {
self::$var[$i] = $v;
}
}
class B extends A {
}
class C extends A {
public function display_stuff($i) {
echo $this->get($i); // works!
}
}
$b = new B();
$c = new C();
$b->set('stuff', 'somestufftodisplay');
$c->display_stuff('stuff');
An example.
I have a parent class that depends on whether child class are instantiated.
class GoogleApp {
protected $auth_token;
public function __construct($scopes) {
$this->auth_token = $scopes;
}
}
class Gmail extends GoogleApp {
public function __construct() {
print_r($this->auth_token);
}
}
$googleApp = new GoogleApp('gmail'); // Change the actual class for all child instances
$gmail = new Gmail();
The idea is that all the children use the same auth_token (which is generated on whether the child classes are used - as of now, I'm just manually adding them to whether I included them in my code). Since I have quite a few child classes (like Calendar or Drive), do I have to inject the parent into each child instance or is there an easier way?
If I understand your request correctly, you're pretty close, you just need to declare your property as static.
class FooParent
{
protected static $scope = null;
public function __construct($scope)
{
self::$scope = $scope;
}
public function getScope()
{
return self::$scope;
}
}
class FooChild extends FooParent
{
public function __construct()
{
if (self::$scope === null) {
throw new Exception('Must set scope first.');
}
}
}
$parent = new FooParent('foo');
$child = new FooChild();
echo $child->getScope(), "\n"; // prints "foo"
I have a PHP Class which requires a unique value in its constructor. If multiple instances of the same class are passed the same value the results are horrific.
How would I go about detecting other instances of a Class so I can check and prevent this from happening before constructing any new ones?
A simple solution would be to keep a static array of the values inside the class. Then, when a new instance is created, check the static array's contents in the constructor.
Something like..
class Foo {
private static $usedValues = array();
public function __construct($val) {
if(in_array($val, self::$usedValues)) {
throw new Exception('blah');
}
self::$usedValues[] = $val;
}
}
I think the multiton pattern is right for you.
class Foo {
static protected $_instances = array();
static public function getInstance($id) {
if(!self::exists($id)) {
self::$_instances[$id] = new Foo($id);
}
return self::$_instances[$id];
}
static public function exists($id) {
return isset(self::$_instances[$id]);
}
protected function __construct($id) {
}
}
I have a parent object that I use for general CRUD in my applications - it has basic save & retrieve methods so I can don't have to reinclude them them in all my objects. Most of my child objects extend this base object. This has worked fine, but I'm finding a problem with retrieving a serialized child object. I use a "retrieve" method in the parent object that creates an instance of the child, then populates itself from the properties of the unserialized child - this means is can "self unserialize" the object.
Only problem is - if the child object has a protected or private property, the parent object can't read it, so it doesn't get picked up during retrieval.
So I'm looking either for a better way to "self unserialize" or a way to allow a parent object to "see" the protected properties - but only during the retrieval process.
Example of the code:
BaseObject {
protected $someparentProperty;
public function retrieve() {
$serialized = file_get_contents(SOME_FILENAME);
$temp = unserialize($serialized);
foreach($temp as $propertyName => $propertyValue) {
$this->$propertyName = $propertyValue;
}
}
public function save() {
file_put_contents(SOME_FILENAME, serialize($this));
}
}
class ChildObject extends BaseObject {
private $unretrievableProperty;
public setProp($val) {
$this->unretrivableProperty = $val;
}
}
$tester = new ChildObject();
$tester->setProp("test");
$tester->save();
$cleanTester = new ChildObject();
$cleanTester->retrieve();
// $cleanTester->unretrievableProperty will not be set
EDITED: Should have said "Private" not protected child properties.
try it like this:
abstract class ParentClass
{
protected abstract function GetChildProperty();
public function main()
{
$value = $this->GetChildProperty();
}
}
class ChildClass extends ParentClass
{
private $priv_prop = "somevalue";
protected function GetChildProperty()
{
return $this->priv_prop;
}
}
It doesn't seem that same class visibility policy applies to iherited/parent classes. The php documentation does not address this.
I would suggest that you declared the retrieve method static, and fetched the $cleanTester through a static call rather than your current "self unserialize" approach.
static function retrieve() {
$serialized = file_get_contents(SOME_FILENAME);
return unserialize($serialized);
}
[...]
$cleanTester = BaseObject::retrieve();
Or you could utilize the __get() method to access inaccessible properties... I believe this could be added to the BaseObject class and fetch protected properties from the child class. Since the same class visibility policy should apply to BaseObject you could define the __get() method private or protected.
BaseObject {
private function __get($propertyName) {
if(property_exists($this,$propertyName))
return $this->{$propertyName};
return null;
}
how about a getProperty() function in the child object that returns $this->unretrievableProperty
The best possible answer to fix this is to use reflections.
Example:
$_SESSION[''] = ''; // init
class Base {
public function set_proxy(){
$reflectionClass = new ReflectionClass($this);
$ar = $reflectionClass->getDefaultProperties();
!isset($ar['host']) or $_SESSION['host'] = $ar['host'];
}
}
class Son1 extends Base {
private $host = '2.2.2.2';
}
class Son2 extends Son1 {
}
$son1 = new Son1();
$son1->set_proxy();
var_dump($_SESSION); // array(2) { [""]=> string(0) "" ["host"]=> string(7) "2.2.2.2" }
unset($_SESSION);
$_SESSION[''] = ''; // init
$son2 = new Son2();
$son2->set_proxy();
var_dump($_SESSION); // array(1) { [""]=> string(0) "" }