Search code examples
phparraysobjectpushpass-by-reference

PHP 7 overwriting an object after pushing it to array results in overwriting of all objects in that array


Hi a good day to everyone. I am new to PHP and here I am trying to push multiple objects ($viewership_prj) to a single array ($viewership_prj_arr).

The first time I wrote the following. The resulting array was not correct.

Basically whenever a new object is pushed into the array, all existing objects in that array are overwritten with the properties of that newly pushed object.

                         $viewership_prj_arr = array();
                         $viewership_prj = (object) array();
                         foreach($projects as $prj)
                         {   
                            /*...*/
                                            
                            $viewership_prj->title = $prj['project_title'];
                            $viewership_prj_arr[] = $viewership_prj;

                            // $viewership_prj_arr[] is [title1] after first loop
                            // [title2, title2] after second loop
                            // [title3, title3, title3] after third loop
                            // ...
                         }

Then I changed to this and it worked. I declared a new object inside each loop.

                         $viewership_prj_arr = array();
                         foreach($projects as $prj)
                         {   
                            /*...*/

                            $viewership_prj = (object) array();
                                            
                            $viewership_prj->title = $prj['project_title'];
                            $viewership_prj_arr[] = $viewership_prj;

                            // $viewership_prj_arr[] is [title1] after first loop
                            // [title1, title2] after second loop
                            // [title1, title2, title3] after third loop
                            // ...
                         }

I was very confused.

The only reason I could think of is that when the object is pushed into the array, it was passed by reference and not by value. I tried looking up the manual https://www.php.net/manual/en/language.oop5.references.php#:~:text=Objects%20and%20references%20%C2%B6,passed%20by%20references%20by%20default%22.&text=A%20PHP%20reference%20is%20an,the%20object%20itself%20as%20value. but it is not making much sense to me.

Could someone help me clarify this? Much thanks in advance.


Solution

  • Oversimplified, PHP stores an object as

    "identifier to object" => actual object
    

    Creating a new object $object_1 = (object)[] stores

    "identifier to object 1" => actual object 1
    
    $object_1 = "identifier to object 1"
    

    The value of $object_1 is a reference to object 1 and NOT the actual object itself. They are two seperate items, linked by the identifier.

    So, when you do

    $object_1->title = "a";
    
    $array[1] = $object_1;
    $array[2] = $object_1;
    $array[3] = $object_1;
    

    Under the hood, it looks like:

    "identifier to object 1" => actual object 1->title = "a"
    
    $array[
      1 => "identifier to object 1",   // (actual object 1->title = "a")
      2 => "identifier to object 1",   // (actual object 1->title = "a")
      3 => "identifier to object 1",   // (actual object 1->title = "a")
      ];
    

    Changing $object_1->title = "b" sets

      "identifier to object 1" => actual object 1->title = "b"
    

    but doesn't change the identifiers in $array. They remain "identifier to object 1", so all the elements of the array are now:

    $array[
      [1] => "identifier to object 1",   // (actual object 1->title = "b")
      [2] => "identifier to object 1",   // (actual object 1->title = "b")
      [3] => "identifier to object 1",   // (actual object 1->title = "b")
      ];
    

    Also $object_2 = $object_1 will only copy the "identifier to object 1". Both variables point to the same object:

    $object_1 = "identifier to object 1"
    $object_2 = "identifier to object 1"
    

    Any variable containing a reference to the actual object 1 can change the actual object 1.

    $object_1->title = "c";
     or
    $object_2->title = "c";
     or
    $array[3]->title = "c";
    

    do all exactly the same, as they all contain a reference to the same actual object 1. So changing one means changing all.

    If you want all array elements to have their own object, you either have to create a new object for each and every one of them or - if you want to use the values of the previous object - use clone.

    $array = [];
    $names = [ "John", "Jack", "Jill" ];
    $object = (object)[];
    $object->name = "";
    $object->coffee = "black with sugar";
    
    foreach( $names as $name ){
      $object->name = $val;      //change the name of the actual object
      $array[] = clone $object;  //create a new object with the current values of $object
      }
    

    Will output

    $array[
     stdClass Object ( name=> John, coffee=> black with sugar ), 
     stdClass Object ( name=> Jack, coffee=> black with sugar ), 
     stdClass Object ( name=> Jill, coffee=> black with sugar ) 
    ]
    //$object only contains the last change!
    $object = stdClass Object ( name=> Jill, coffee=> black with sugar )