How to clone an array of objects in PHP?
Asked Answered
L

16

69

I have an array of objects. I know that objects get assigned by "reference" and arrays by "value". But when I assign the array, each element of the array is referencing the object, so when I modify an object in either array the changes are reflected in the other.

Is there a simple way to clone an array, or must I loop through it to clone each object?

Lumber answered 20/6, 2011 at 23:59 Comment(1)
References to objects are assigned by value.</unnecessarypedantry>Part
P
64

References to the same objects already get copied when you copy the array. But it sounds like you want to shallow-copy deep-copy the objects being referenced in the first array when you create the second array, so you get two arrays of distinct but similar objects.

The most intuitive way I can come up with right now is a loop; there may be simpler or more elegant solutions out there:

$new = array();

foreach ($old as $k => $v) {
    $new[$k] = clone $v;
}
Part answered 21/6, 2011 at 0:3 Comment(7)
What he's doing right now is shallow copying, he wants to deep copy. Your code is right, but your terminology isn't, though.Easygoing
@Kristian Antonsen: Am I not shallow-copying the objects rather than just shallow-copying the array?Part
@BoltClock: Whether you are shallow-coyping the objects depends on the implementation of clone of the specific object; that's up to the designer of the object, not the user. For instance, if one of the objects in the array contains references to another object and a proper __clone function hasn't been implemented, it wil still only be a shallow copy. In other words: If the developer of the object you're cloning did it right, it'll be a deep copy.Easygoing
@Part no, you deep-copying array and elements of array. Your code in answer is correct, anyway.Prophase
Got it, I forgot PHP provides a __clone() method for implementing deep copy, and was assuming default shallow copy behavior with clone.Part
@Lumber just to know: clone not always can create independent copy, read the manual: Any properties that are references to other variables, will remain references.Prophase
I only need a shallow copy of each object (the objects only contain values) so this answer solves the problem.Lumber
I
66
$array = array_merge(array(), $myArray);
Inject answered 17/7, 2012 at 13:39 Comment(9)
That is very smart! Much cleaner than a foreach loop!Buccinator
Is this actually different to $array = $myArray? See #1533118Sulfanilamide
@GeorgeLund No, this is not any different. See php.net/manual/en/function.array-merge.php#92346Fleer
Queue Spongebob narrator, "Six hours later..." I have a different problem, and my journey has taken me back here. Who would have thunk? I needed a way to clone an array while preserving the references all while leaving the internal pointer untouched. This does exactly that. +1.Fleer
After a good night's rest, I came to a realization that I didn't need array_merge(). I've added an answer to a similar question with my solution: https://mcmap.net/q/63844/-is-there-a-function-to-make-a-copy-of-a-php-array-to-anotherFleer
In this solution we lost original numeric keys from $myArray: "Values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array."Schaab
This is not cloning the objects in the array (like it was asked)! So I don't understand this answer and the high voting? It is nearly the same like $arrB = $arrA, just that numeric keys are getting reindexed.Binny
Smarter: $array = (array)$myArray;Chaps
Smartest: reading the question carefully, and realizing this answer does not do what the question asks - nor do the variations given in these comments!Infusive
P
64

References to the same objects already get copied when you copy the array. But it sounds like you want to shallow-copy deep-copy the objects being referenced in the first array when you create the second array, so you get two arrays of distinct but similar objects.

The most intuitive way I can come up with right now is a loop; there may be simpler or more elegant solutions out there:

$new = array();

foreach ($old as $k => $v) {
    $new[$k] = clone $v;
}
Part answered 21/6, 2011 at 0:3 Comment(7)
What he's doing right now is shallow copying, he wants to deep copy. Your code is right, but your terminology isn't, though.Easygoing
@Kristian Antonsen: Am I not shallow-copying the objects rather than just shallow-copying the array?Part
@BoltClock: Whether you are shallow-coyping the objects depends on the implementation of clone of the specific object; that's up to the designer of the object, not the user. For instance, if one of the objects in the array contains references to another object and a proper __clone function hasn't been implemented, it wil still only be a shallow copy. In other words: If the developer of the object you're cloning did it right, it'll be a deep copy.Easygoing
@Part no, you deep-copying array and elements of array. Your code in answer is correct, anyway.Prophase
Got it, I forgot PHP provides a __clone() method for implementing deep copy, and was assuming default shallow copy behavior with clone.Part
@Lumber just to know: clone not always can create independent copy, read the manual: Any properties that are references to other variables, will remain references.Prophase
I only need a shallow copy of each object (the objects only contain values) so this answer solves the problem.Lumber
G
28

You need to clone objects to avoid having references to the same object.

function array_copy($arr) {
    $newArray = array();
    foreach($arr as $key => $value) {
        if(is_array($value)) $newArray[$key] = array_copy($value);
        else if(is_object($value)) $newArray[$key] = clone $value;
        else $newArray[$key] = $value;
    }
    return $newArray;
}
Gainless answered 21/6, 2011 at 0:11 Comment(2)
Simple, yet effective. I use it as a static function.Sinuosity
+1 I accidentally stole your function without realizing it: https://mcmap.net/q/63844/-is-there-a-function-to-make-a-copy-of-a-php-array-to-another. I've been to this page a number of times, but I always scroll down until I found some answers with high vote counts (thus skipping yours). It's almost the same exact function, and I've never even glanced at your answer until just now!Fleer
G
25

As suggested by AndreKR, using array_map() is the best way to go if you already know that your array contains objects:

$clone = array_map(function ($object) { return clone $object; }, $array);
Gomes answered 31/10, 2013 at 8:52 Comment(0)
P
7

I opted for clone as well. Cloning an array does not work (you could consider some arrayaccess implementation to do so for you), so as for the array clone with array_map:

class foo {
    public $store;
    public function __construct($store) {$this->store=$store;}
}

$f = new foo('moo');
$a = array($f);

$b = array_map(function($o) {return clone $o;}, $a);

$b[0]->store='bar';    
var_dump($a, $b);

Array clone with serialize and unserialize

If your objects support serialisation, you can even sort of deep shallow copy/clone with a tour into their sleeping state and back:

$f = new foo('moo');
$a = array($f);

$b = unserialize(serialize($a));

$b[0]->store='bar';
var_dump($a, $b);

However, that can be a bit adventurous.

Postnatal answered 21/6, 2011 at 0:9 Comment(0)
M
4

A pure PHP 7.4 >= solution:

$cloned = array_map(fn ($o) => clone $o, $original);
Miriam answered 11/4, 2020 at 19:43 Comment(0)
S
2

You need to loop it (possibly using a function like array_map() for that), there is no PHP function to automatically perform a deep copy of an array.

Schifra answered 21/6, 2011 at 0:3 Comment(0)
D
2

I've done it like this:

function array_clone($array) {
    array_walk_recursive($array, function(&$value) {
        if(is_object($value)) {
            $value = clone $value;
        }
    });
    return $array;
}

The function arg copies the array without cloning the objects, then each nested object is cloned. So it won't work if the algorithm is not used inside a function.

Note this function clone the array recursively. You can use array_walk instead of array_walk_recursive if you do not want this to happen.

Duron answered 11/3, 2014 at 7:7 Comment(0)
H
2

Here is my best practice on an array of objects and cloning. Usually it is a good idea, to have a Collection class for each class of objects (or interface), which are used in an array. With the magic function __clone cloning becomes a formalized routine:

class Collection extends ArrayObject
{
     public function __clone()
     {
        foreach ($this as $key => $property) {
            $this[$key] = clone $property;
        }
     }
}

To clone your array, use it as Collection and then clone it:

$arrayObject = new Collection($myArray);
$clonedArrayObject = clone $arrayObject;

One step further, you should add a clone method to your class and each sub-class, too. This is important for deep cloning, or you might have unintended side effects:

class MyClass
{
     public function __clone()
     {
        $this->propertyContainingObject = clone $this->propertyContainingObject;
     }
}

An important note on using ArrayObject is, that you cannot use is_array() any longer. So be aware of this on refactoring your code.

Hamza answered 18/11, 2016 at 16:1 Comment(0)
S
1

or also

$nuarr = json_decode(json_encode($array));

but it is expensive, I prefer Sebastien version (array_map)

Sclerite answered 19/12, 2014 at 10:39 Comment(0)
C
1

Objects are passed by pointed by default and are not always easy to clone especially as they may have circular references. You would be better suited with a different choice of data structures.

For those providing solutions to shallow copy the easier way is this:

 $b = (array)$a;

For deep copies I do not recommend this solution:

$nuarr = json_decode(json_encode($array));

This is for a deep copy. It only supports a subset of PHP types and will swap objects to array or arrays to objects which might not be what you want as well as potentially corrupting binary values and so on.

If you make a manual recursive function for deep copies the memory usage will be much less afterwards for scalar values and keys so using json or any serializer an impact beyond its point of execution.

It may be better to use unserialize(serialize($a)) for deep copies if performance is not a concern which has wider support for things such as objects though I would not be surprised if it breaks for circular references and several other unusual things.

array_merge_recursive or array_walk_recursive can also be used for arrays.

You can easily create your own recursive function that uses is_object and is_array to choose the appropriate means of copying.

Chaps answered 26/10, 2015 at 11:58 Comment(0)
T
1

For PHP 5 and above one can use ArrayObject cunstructur to clone an array like the following:

$myArray = array(1, 2, 3);
$clonedArray = new ArrayObject($myArray);
Throttle answered 23/5, 2018 at 9:21 Comment(1)
no need for new object explicitly, just call copy method on the original to get the clone. See: php.net/manual/en/arrayobject.getarraycopy.phpMonocular
H
0

If you have multidimensional array or array composed of both objects and other values you can use this method:

$cloned = Arr::clone($array);

from that library.

Hooch answered 6/9, 2018 at 11:57 Comment(0)
F
0

Just include this function in all of your classes. This will do a deep clone of all objects in case if you have arrays of objects within the object itself. It will trigger all of the __clone() functions in these classes:

/**
 * Clone the object and its properties
 */
public function __clone()
{
    foreach ($this as $key => $property)
    {
        if(is_array($property))
        {
            foreach ($property as $i => $o)
            {
                if(is_object($o)) $this->$key[$i] = clone $o;
                else $this->$key[$i] = $o;
            }
        }
        else if(is_object($property)) $this->$key = clone $property;
        else $this->$key = $property;
    }
}
Fibula answered 9/12, 2019 at 19:47 Comment(0)
D
0
$a = ['a'=>'A','b'=>'B','c'=>'C'];
$b = $a+[];
$a['a'] = 'AA'; // modifying array $a
var_export($a);
var_export($b); 

Result:

array ( 'a' => 'AA', 'b' => 'B', 'c' => 'C', )
array ( 'a' => 'A', 'b' => 'B', 'c' => 'C', )
Degrease answered 17/12, 2019 at 4:31 Comment(2)
The elements in these arrays are scalar values, so are not object references. This does not answer the question.Algor
This is an excellent solution if you have all scalar values in an array OR you want a shallow copy in case of objects.Louralourdes
G
0

i prefer recursive way:

function deepClone(mixed $object): mixed
{
    switch (gettype($object)) {
        case 'object':
            return clone $object;

        case 'array':
            $ret = [];
            foreach ($object as $key => $item) {
                $ret[$key] = deepClone($item);
            }
            return $ret;

        default:
            return $object;
    }
}

deepClone($array);
Greatniece answered 14/9, 2021 at 14:35 Comment(1)
Please don't post only code as an answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotesHieronymus

© 2022 - 2024 — McMap. All rights reserved.