PHP Cloning Children - php

I have an object that is a somewhat basic tree. I needed to make a deep copy of it and found myself implementing the __clone method. The successful code is:
function __clone() {
$object = new CustomXML($this->rootElement);
foreach ($this->elements as $key => $element) {
$this->elements[$key] = clone $this->elements[$key];
$object->elements[$key] = $this->elements[$key];
}
$object->attributes = $this->attributes;
$object->value = $this->value;
$object->allowHTML = $this->allowHTML;
$object->endTag = $this->endTag;
$object->styles = $this->styles;
$object->childID = $this->childID;
return $object;
}
My question is... Why do I have to use
$this->elements[$key] = clone $this->elements[$key];
$object->elements[$key] = $this->elements[$key];
Why can't I just use
$object->elements[$key] = clone $this->elements[$key];
Using the second one still leaves a reference to the children. Why is this? The values in $this->elements are of the same class.

__clone() is invoked on an already-created shallow copy of an object. See the documentation
PHP will shallow-copy all properties and create a new object without calling its constructor (similar to serialization and deserialization). Then PHP calls __clone() on the new object so you can modify it according to your whims. Because it modifies the object, it should not return anything.
Your code (if you always want a deep copy) should look like this:
function __clone() {
foreach ($this->children as $key => $child) {
$this->children[$key] = clone $this->children[$key];
}
}
However I strongly recommend that you do not do this! Instead of relying on the clone keyword, add methods to return cloned objects explicitly (e.g., DOMNode::cloneNode(). This will, for example, let you control whether your copies should be shallow or deep. If you just use clone you cannot control this.
Here is an example:
interface DeepCopyable
{
/**
* Return a copy of the current object
*
* #param $deep bool If TRUE, return a deep copy
* #return object
*/
public function copy($deep=false);
}
class TreeNode implements DeepCopyable
{
private $I_AM_A_CLONE = false;
protected $children = array();
function __clone() {
$this->I_AM_A_CLONE = true;
}
public function addChild(Copyable $child) {
$this->children[] = $child;
}
public function copy($deep=false) {
$copy = clone $this;
if ($deep) {
foreach ($this->children as $n => $child) {
$copy->children[$n] = $child->copy($deep);
}
}
return $copy;
}
}
$a = new TreeNode();
$a->addChild(new TreeNode());
var_dump($a);
var_dump($a->copy());
var_dump($a->copy(true));
This example also illustrates the proper use of __clone(). You need to add a clone magic method when a private or protected property of the cloned object should not be identical to the original. For example, if you have an id property on an object which should be unique, you may want to ensure that a clone will not have the same ID and you never ever want calling code to control that. Or a "dirty" flag, or whatever.

After a long review of this, I created a test scenario and realized that I was not understanding the clone method at all.
Consider this example code:
<?php
class A {
function __construct($value = "1") {
$this->value = $value;
$this->children = array();
}
function addChild() {
$this->children[] = new A(count($this->children));
}
function __clone() {
foreach ($this->children as $key => $child) {
$this->children[$key] = clone $this->children[$key];
//$object->children[$key] = clone $this->children[$key];
}
}
}
$a = new A();
$a->addChild();
$b = clone $a;
var_dump($b);
$b->value = "test";
$b->children[0]->value = "test2";
var_dump($a);
var_dump($b);
The clone method is being called on $b, not on $a. So essentially, calling $this->children[$key] = clone $this->children[$key]; is breaking the reference. Returning a value is pointless here. In summary, my code should have looked like this:
foreach ($this->elements as $key => $element) {
$this->elements[$key] = clone $this->elements[$key];
}
You might say that calling $b = clone $a; is equivalent to doing:
$b = $a;
$b->__clone();

Related

PHP | is it possible to set new class instance to be the same as exists instance?

I'm currently looking for a way to set a new instance to be the same as an exists instance if already exists.
Is there any easy way as doing something like:
Class Check {
public static $instance = null;
public $name = "";
function __construct($name) {
if (self::$instance !== null) {
$this = self::$instance;
} else {
$this->name = $name;
self::$instance = $this;
}
}
}
$a = new Check("A");
$b = new Check("B");
echo $a->name." = ".$b->name;
to achieve it?
what it returns:
A =
What I want it to return:
A = A
Is it possible to accomplish that?
I want that all variables of the older instance to maintain the same.
Thanks in advance.
This is the only two ways I found to achieve it
#1 Using get_object_vars
You can use get_object_vars that returns all public variables that a specific object contains, then you can iterate them using foreach
example:
if (self::$instance !== null) {
foreach (get_object_vars(self::$instance) as $prop=>$value) {
$this->{$prop} = $value;
}
}
#2 Is using PHP reflection, I find it less useful in my case because it doesn't reflect properties that was generated on the go
Note: Reflection will reflect also properties such as private|static and so. you can filter them inside the getProperties function or iterate through all properties and use boolean methods filter between them.
example:
if (self::$instance !== null) {
$reflection = new ReflectionClass(self::$instance);
$props = $reflection->getProperties();
foreach ($props as $prop) {
$this->{$prop->name} = $prop->getValue(self::$instance);
}
}
so if you do self::$instance->newVar = 1;
it won't be reflected. but in the #1 Option it will be.
Hope it will help someone out there.

How do I list all objects created from a class in PHP [duplicate]

I would like to get all the instances of an object of a certain class.
For example:
class Foo {
}
$a = new Foo();
$b = new Foo();
$instances = get_instances_of_class('Foo');
$instances should be either array($a, $b) or array($b, $a) (order does not matter).
A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.
One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.
If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.
class TrackableObject
{
protected static $_instances = array();
public function __construct()
{
self::$_instances[] = $this;
}
public function __destruct()
{
unset(self::$_instances[array_search($this, self::$_instances, true)]);
}
/**
* #param $includeSubclasses Optionally include subclasses in returned set
* #returns array array of objects
*/
public static function getInstances($includeSubclasses = false)
{
$return = array();
foreach(self::$_instances as $instance) {
if ($instance instanceof get_class($this)) {
if ($includeSubclasses || (get_class($instance) === get_class($this)) {
$return[] = $instance;
}
}
}
return $return;
}
}
The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)
Here's a possible solution:
function get_instances_of_class($class) {
$instances = array();
foreach ($GLOBALS as $value) {
if (is_a($value, $class) || is_subclass_of($value, $class)) {
array_push($instances, $value);
}
}
return $instances;
}
Edit: Updated the code to check if the $class is a superclass.
Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:
function get_instances_of_class($class, $vars=null) {
if ($vars == null) {
$vars = $GLOBALS;
}
$instances = array();
foreach ($vars as $value) {
if (is_a($value, $class)) {
array_push($instances, $value);
}
$object_vars = get_object_vars($value);
if ($object_vars) {
$instances = array_merge($instances, get_instances_of_class($class, $object_vars));
}
}
return $instances;
}
I'm not sure if it can go into infinite recursion with certain objects, so beware...
I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).
I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.
As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.

How can I create a PHP class where one of its functions it to clone itself?

Basically, I need to store past versions of this object in its current state in an array. Something like:
public class MyClass{
$n = 0;
$array = array()
storeOldVersion(){
$this->array[] = clone $this;
}
}
I know I do something like "clone $this->n", but how can I literally clone the MyClass object itself, and store it into an array the MyClass object holds?
Thanks
What you proposed actually works, except you had some incorrect code. The following version works:
class MyClass{
protected $n = 0;
public $array = array();
public function storeOldVersion(){
$this->array[] = clone $this;
$this->n = 2;
}
}
$a = new MyClass();
$a->storeOldVersion();
echo "<pre>";
var_dump( $a ); // retuns class with n = 2
var_dump( $a->array ); // return array with one class having n = 0
A word of warning though
If you call storeOldVersion() more than once, as is, it will recursively clone the "array" that contains other clones and your object could get pretty massive exponentially. You should probably unset the array variable from the clone before storing it in the array..
e.g.
$clone = clone $this;
$clone->array = array();
$this->array[] = $clone;
You can also try serialization for complicated objects. It will store the object as a string. After all you can unserialize string to object with the past state.
http://php.net/manual/en/language.oop5.serialization.php
With a little cleanup, your code works. However, you probably want to clean out the $array of old versions in the old versions you store, otherwise you're going to end up with a huge recursive mess on your hands.
<?php
class MyClass{
private $n = 0;
private $array = array();
public function storeOldVersion(){
$clone = clone $this;
$clone->array = null;
$this->array[] = $clone;
}
}
$c = new MyClass();
$c->storeOldVersion();
var_dump($c);

What is the best method to merge two PHP objects?

We have two PHP5 objects and would like to merge the content of one into the second. There are no notion of subclasses between them so the solutions described in the following topic cannot apply.
How do you copy a PHP object into a different object type
//We have this:
$objectA->a;
$objectA->b;
$objectB->c;
$objectB->d;
//We want the easiest way to get:
$objectC->a;
$objectC->b;
$objectC->c;
$objectC->d;
Remarks:
These are objects, not classes.
The objects contain quite a lot of fields so a foreach would be quite slow.
So far we consider transforming objects A and B into arrays then merging them using array_merge() before re-transforming into an object but we can't say we are proud if this.
If your objects only contain fields (no methods), this works:
$obj_merged = (object) array_merge((array) $obj1, (array) $obj2);
This actually also works when objects have methods. (tested with PHP 5.3 and 5.6)
foreach($objectA as $k => $v) $objectB->$k = $v;
You could create another object that dispatches calls to magic methods to the underlying objects. Here's how you'd handle __get, but to get it working fully you'd have to override all the relevant magic methods. You'll probably find syntax errors since I just entered it off the top of my head.
class Compositor {
private $obj_a;
private $obj_b;
public function __construct($obj_a, $obj_b) {
$this->obj_a = $obj_a;
$this->obj_b = $obj_b;
}
public function __get($attrib_name) {
if ($this->obj_a->$attrib_name) {
return $this->obj_a->$attrib_name;
} else {
return $this->obj_b->$attrib_name;
}
}
}
Good luck.
I understand that using the generic objects [stdClass()] and casting them as arrays answers the question, but I thought the Compositor was a great answer. Yet I felt it could use some feature enhancements and might be useful for someone else.
Features:
Specify reference or clone
Specify first or last entry to take precedence
Multiple (more than two) object merging with syntax similarity to array_merge
Method linking: $obj->f1()->f2()->f3()...
Dynamic composites: $obj->merge(...) /* work here */ $obj->merge(...)
Code:
class Compositor {
protected $composite = array();
protected $use_reference;
protected $first_precedence;
/**
* __construct, Constructor
*
* Used to set options.
*
* #param bool $use_reference whether to use a reference (TRUE) or to copy the object (FALSE) [default]
* #param bool $first_precedence whether the first entry takes precedence (TRUE) or last entry takes precedence (FALSE) [default]
*/
public function __construct($use_reference = FALSE, $first_precedence = FALSE) {
// Use a reference
$this->use_reference = $use_reference === TRUE ? TRUE : FALSE;
$this->first_precedence = $first_precedence === TRUE ? TRUE : FALSE;
}
/**
* Merge, used to merge multiple objects stored in an array
*
* This is used to *start* the merge or to merge an array of objects.
* It is not needed to start the merge, but visually is nice.
*
* #param object[]|object $objects array of objects to merge or a single object
* #return object the instance to enable linking
*/
public function & merge() {
$objects = func_get_args();
// Each object
foreach($objects as &$object) $this->with($object);
// Garbage collection
unset($object);
// Return $this instance
return $this;
}
/**
* With, used to merge a singluar object
*
* Used to add an object to the composition
*
* #param object $object an object to merge
* #return object the instance to enable linking
*/
public function & with(&$object) {
// An object
if(is_object($object)) {
// Reference
if($this->use_reference) {
if($this->first_precedence) array_push($this->composite, $object);
else array_unshift($this->composite, $object);
}
// Clone
else {
if($this->first_precedence) array_push($this->composite, clone $object);
else array_unshift($this->composite, clone $object);
}
}
// Return $this instance
return $this;
}
/**
* __get, retrieves the psudo merged object
*
* #param string $name name of the variable in the object
* #return mixed returns a reference to the requested variable
*
*/
public function & __get($name) {
$return = NULL;
foreach($this->composite as &$object) {
if(isset($object->$name)) {
$return =& $object->$name;
break;
}
}
// Garbage collection
unset($object);
return $return;
}
}
Usage:
$obj = new Compositor(use_reference, first_precedence);
$obj->merge([object $object [, object $object [, object $...]]]);
$obj->with([object $object]);
Example:
$obj1 = new stdClass();
$obj1->a = 'obj1:a';
$obj1->b = 'obj1:b';
$obj1->c = 'obj1:c';
$obj2 = new stdClass();
$obj2->a = 'obj2:a';
$obj2->b = 'obj2:b';
$obj2->d = 'obj2:d';
$obj3 = new Compositor();
$obj3->merge($obj1, $obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj2:a, obj2:b, obj1:c, obj2:d
$obj1->c;
$obj3 = new Compositor(TRUE);
$obj3->merge($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, obj1:c, obj2:d
$obj1->c = 'obj1:c';
$obj3 = new Compositor(FALSE, TRUE);
$obj3->with($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, #obj1:c, obj2:d
$obj1->c = 'obj1:c';
A very simple solution considering you have object A and B:
foreach($objB AS $var=>$value){
$objA->$var = $value;
}
That's all. You now have objA with all values from objB.
The \ArrayObject class has the possibility to exchange the current array to disconnect the original reference. To do so, it comes with two handy methods: exchangeArray() and getArrayCopy(). The rest is plain simple array_merge() of the provided object with the ArrayObjects public properties:
class MergeBase extends ArrayObject
{
public final function merge( Array $toMerge )
{
$this->exchangeArray( array_merge( $this->getArrayCopy(), $toMerge ) );
}
}
The usage is as easy as this:
$base = new MergeBase();
$base[] = 1;
$base[] = 2;
$toMerge = [ 3,4,5, ];
$base->merge( $toMerge );
a solution To preserve,both methods and properties from merged onjects is to create a combinator class that can
take any number of objects on __construct
access any method using __call
accsess any property using __get
class combinator{
function __construct(){
$this->melt = array_reverse(func_get_args());
// array_reverse is to replicate natural overide
}
public function __call($method,$args){
forEach($this->melt as $o){
if(method_exists($o, $method)){
return call_user_func_array([$o,$method], $args);
//return $o->$method($args);
}
}
}
public function __get($prop){
foreach($this->melt as $o){
if(isset($o->$prop))return $o->$prop;
}
return 'undefined';
}
}
simple use
class c1{
public $pc1='pc1';
function mc1($a,$b){echo __METHOD__." ".($a+$b);}
}
class c2{
public $pc2='pc2';
function mc2(){echo __CLASS__." ".__METHOD__;}
}
$comb=new combinator(new c1, new c2);
$comb->mc1(1,2);
$comb->non_existing_method(); // silent
echo $comb->pc2;
I would go with linking the second object into a property of the first object. If the second object is the result of a function or method, use references. Ex:
//Not the result of a method
$obj1->extra = new Class2();
//The result of a method, for instance a factory class
$obj1->extra =& Factory::getInstance('Class2');
To merge any number of raw objects
function merge_obj(){
foreach(func_get_args() as $a){
$objects[]=(array)$a;
}
return (object)call_user_func_array('array_merge', $objects);
}
This snippet of code will recursively convert that data to a single type (array or object) without the nested foreach loops. Hope it helps someone!
Once an Object is in array format you can use array_merge and convert back to Object if you need to.
abstract class Util {
public static function object_to_array($d) {
if (is_object($d))
$d = get_object_vars($d);
return is_array($d) ? array_map(__METHOD__, $d) : $d;
}
public static function array_to_object($d) {
return is_array($d) ? (object) array_map(__METHOD__, $d) : $d;
}
}
Procedural way
function object_to_array($d) {
if (is_object($d))
$d = get_object_vars($d);
return is_array($d) ? array_map(__FUNCTION__, $d) : $d;
}
function array_to_object($d) {
return is_array($d) ? (object) array_map(__FUNCTION__, $d) : $d;
}
All credit goes to: Jason Oakley
Here is a function that will flatten an object or array. Use this only if you are sure your keys are unique. If you have keys with the same name they will be overwritten. You will need to place this in a class and replace "Functions" with the name of your class. Enjoy...
function flatten($array, $preserve_keys=1, &$out = array(), $isobject=0) {
# Flatten a multidimensional array to one dimension, optionally preserving keys.
#
# $array - the array to flatten
# $preserve_keys - 0 (default) to not preserve keys, 1 to preserve string keys only, 2 to preserve all keys
# $out - internal use argument for recursion
# $isobject - is internally set in order to remember if we're using an object or array
if(is_array($array) || $isobject==1)
foreach($array as $key => $child)
if(is_array($child))
$out = Functions::flatten($child, $preserve_keys, $out, 1); // replace "Functions" with the name of your class
elseif($preserve_keys + is_string($key) > 1)
$out[$key] = $child;
else
$out[] = $child;
if(is_object($array) || $isobject==2)
if(!is_object($out)){$out = new stdClass();}
foreach($array as $key => $child)
if(is_object($child))
$out = Functions::flatten($child, $preserve_keys, $out, 2); // replace "Functions" with the name of your class
elseif($preserve_keys + is_string($key) > 1)
$out->$key = $child;
else
$out = $child;
return $out;
}
Let's keep it simple!
function copy_properties($from, $to, $fields = null) {
// copies properties/elements (overwrites duplicates)
// can take arrays or objects
// if fields is set (an array), will only copy keys listed in that array
// returns $to with the added/replaced properties/keys
$from_array = is_array($from) ? $from : get_object_vars($from);
foreach($from_array as $key => $val) {
if(!is_array($fields) or in_array($key, $fields)) {
if(is_object($to)) {
$to->$key = $val;
} else {
$to[$key] = $val;
}
}
}
return($to);
}
If that doesn't answer your question, it will surely help towards the answer.
Credit for the code above goes to myself :)

Get all instances of a class in PHP

I would like to get all the instances of an object of a certain class.
For example:
class Foo {
}
$a = new Foo();
$b = new Foo();
$instances = get_instances_of_class('Foo');
$instances should be either array($a, $b) or array($b, $a) (order does not matter).
A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.
One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.
If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.
class TrackableObject
{
protected static $_instances = array();
public function __construct()
{
self::$_instances[] = $this;
}
public function __destruct()
{
unset(self::$_instances[array_search($this, self::$_instances, true)]);
}
/**
* #param $includeSubclasses Optionally include subclasses in returned set
* #returns array array of objects
*/
public static function getInstances($includeSubclasses = false)
{
$return = array();
foreach(self::$_instances as $instance) {
if ($instance instanceof get_class($this)) {
if ($includeSubclasses || (get_class($instance) === get_class($this)) {
$return[] = $instance;
}
}
}
return $return;
}
}
The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)
Here's a possible solution:
function get_instances_of_class($class) {
$instances = array();
foreach ($GLOBALS as $value) {
if (is_a($value, $class) || is_subclass_of($value, $class)) {
array_push($instances, $value);
}
}
return $instances;
}
Edit: Updated the code to check if the $class is a superclass.
Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:
function get_instances_of_class($class, $vars=null) {
if ($vars == null) {
$vars = $GLOBALS;
}
$instances = array();
foreach ($vars as $value) {
if (is_a($value, $class)) {
array_push($instances, $value);
}
$object_vars = get_object_vars($value);
if ($object_vars) {
$instances = array_merge($instances, get_instances_of_class($class, $object_vars));
}
}
return $instances;
}
I'm not sure if it can go into infinite recursion with certain objects, so beware...
I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).
I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.
As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.

Categories