Popcorn
Popcorn

Reputation: 5348

How does PHP references work under the hood for arrays?

I'm reading this article about PHP variable references: http://derickrethans.nl/talks/phparch-php-variables-article.pdf and wanted to check if my understanding is correct, regarding when new variable containers get created.

For non-arrays, variable containers get created whenever you assign a variable that is not pointing to a container with is_ref set.

Ex 1 (let {..} be a container):
$a = 1;     // "a" => {integer, 1, is_ref = 0, ref_count = 1}

$b = $a;    // "a", "b" => {integer, 1, is_ref = 0, ref_count = 2}

$b = 2;     // "a" => {integer, 1, is_ref = 0, ref_count = 1}
            // "b" => {integer, 2, is_ref = 0, ref_count = 1}

Ex 2:
$a = 1;     // "a" => {integer, 1, is_ref = 0, ref_count = 1}

$b = &$a;    // "a", "b" => {integer, 1, is_ref = 1, ref_count = 2}

$b = 2;     // "a", "b" => {integer, 2, is_ref = 1, ref_count = 2}

How does it work for arrays? It doesn't look like the same thing applies. For example,

$a = array(1, 2, 3);  
$b = $a;
$c = &$b[2];
$c = 4;
print_r($a); // prints (1, 2, 3) instead of (1, 2, 4)
print_r($b); // prints (1, 2, 4)

My expectation:

$a and $b points to the same container. Within this container, we have 3 numeric_keys "0", "1", "2" that point to containers for integers 1, 2, and 3 respectively.

When we do $c = &$b[2], we update the container containing integer 3:

When we do $c = 4, we update the container containing integer 3:

However, something is wrong with my expectation because $a[2] != 4 at the end. I'm trying to figure out why. My best guess is that when we try to reference elements of an array, or properties of an object, the PHP engine first checks the array / object itself to see if is_ref = 1. If it is, everything works according to my expectations. If is_ref = 0, then something else happens, which is what I'm seeing. Can someone fill me in on what that "something else" is?

EDIT Looks like this is what's actually going on. This code should clarify everything!

$a = array(1, 2, 3);
$b = $a;
$c = &$b[2];      // $b points to a new container where $b[0], $b[1] still point to same container as $a[0], $a[1], but $b[2] points to a new container also pointed to by $c
$d = $b;        // $d points to $b's container, this means changing $c will also change $d[2]      
$d[0] = 5;      // The container pointed to by $d[0] is the same as the one pointed to by $a[0] and $b[0]. Since this container has is_ref = 0, $d[0] will now point to a new container

// At this point $a = (1, 2, 3), $b = (1, 2, 3), $c = 3, $d = (5, 2, 3)

$d[2] = 25;     // The container pointed to by $d[2] is the same as the one pointed to by $b[2] and $c. Since this container has is_ref = 1, Changing $d[2] will affect both $b[2] and $c.

// At this point $a = (1, 2, 3), $b = (1, 2, 25), $c = 25, $d = (5, 2, 25)

$e = $d[2];     // Since $d[2]'s container has is_ref = 1, $e will point to its own container

$c = 4;         // Same idea as $d[2] = 25; except $e won't get affected

// At this point $a = (1, 2, 3), $b = (1, 2, 4), $c = 4, $d = (5, 2, 4), $e = 25

// only way to have $d[2] be different from $b[2] is to make the container's is_ref = 0
unset($b[2]);
unset($c);
$b[2] = $d[2];
$d[2] = 55;

// At this point $a = (1, 2, 3), $b = (1, 2, 4), $d = (5, 2, 25), $e = 25

Upvotes: 8

Views: 695

Answers (1)

Machavity
Machavity

Reputation: 31654

What you created $a it was a simple variable. But when you created $b, by default, PHP copied the variable. So $b is now totally separate from $a, just like it was in your first example.

Then you set $c equal to the reference to $b[2]. So they are both pointing at the same memory address. Update one and it updates the other. The problem is you think that $a should be updated as well, but it shouldn't be because $b is its own variable. Consider what happens when we change $b to a reference to $a

$a = array(1, 2, 3);  
$b = &$a;
$c = &$b[2];
$c = 4;
print_r($a); // prints (1, 2, 4)
print_r($b); // prints (1, 2, 4)

This works like you describe because $b and $a reference the same thing (technically $b is now a symbol pointing to $a)

If you want to dive even deeper into the subject here's an excellent article that covers it in depth. http://webandphp.com/how-php-manages-variables

Upvotes: 2

Related Questions