Search code examples
phpclone

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"
      }
    }