EN VI

PHP Cloning an object when a reference exists breaks the clone?

2024-03-12 16:00:12
PHP Cloning an object when a reference exists breaks the clone

If I deep clone an object that contains another object while I have a reference in a variable of the inner property, the clone will not be deep.

<?php

class Person
{
    public function __construct(
        public string $name,
    ) {  
    }
}

class Country
{
    public function __construct(
        public Person $person,
    ) {
    }
    public function __clone()
    {
        $this->person = clone $this->person;
    }
}
$usa = new Country(new Person('Arthur'));

$blah = &$usa->person;
$italy = clone $usa;
$italy->person->name = 'jack';


var_dump($usa);
var_dump($italy);

This line cause $usa and $italy to contain the same Person property. Doing unset $blah before cloning $usa works, but why ?

$blah = &$usa->person;

Outputs :

object(Country)#1 (1) {
  ["person"]=>
  &object(Person)#4 (1) {
    ["name"]=>
    string(4) "jack"
  }
}
object(Country)#3 (1) {
  ["person"]=>
  &object(Person)#4 (1) {
    ["name"]=>
    string(4) "jack"
  }
}

Solution:

It is related on how php treats "Reference"

you can find more info here:

https://www.php.net/manual/en/language.references.whatare.php

and here:

https://www.php.net/manual/en/language.references.arent.php

but to make things short when you create a reference php convert both variables (object or whatever) from 'value types' to 'reference type'

so if you create a reference and after make a clone since both $bla e $usa->person are 'reference type' you will end up cloning the reference

if you delete all references except one php will convert the last object/variable into a 'value type' again.

one last thing ... i dont know any way to identify 'reference types' at runtime by code and i dont think there's one. but if you var_dump your variables you'll identify them easily:

object(Country)#1 (1) {
  ["person"]=>
  //'value type' since no & prefix
  //==============================
  object(Person)#2 (1) {
    ["name"]=>
    string(6) "Arthur"
  }
}
object(Country)#3 (1) {
  ["person"]=>
  //'value type' since no & prefix
  //==============================
  object(Person)#4 (1) {
    ["name"]=>
    string(4) "jack"
  }
}  




object(Country)#1 (1) {
  ["person"]=>
  //'reference type' ===> & prefix
  //==============================  
  &object(Person)#4 (1) {
    ["name"]=>
    string(4) "jack"
  }
}
object(Country)#3 (1) {
  ["person"]=>
  //'reference type' ===> & prefix
  //==============================
  &object(Person)#4 (1) {
    ["name"]=>
    string(4) "jack"
  }
}
Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login