In PHP, as the = operator makes a copy of the variable being assigned, so one (and when I say "one", I mean "the person asking the question, and myself as well") might be surprised to see the answer is "22", rather expecting it to be "20". Surely $array1[1] is discrete from $array2[1], and $array1[1] should not be impacted by the change to $array2[1]. $x is not a macguffin in this: remove that line, and things behave "as expected". How is making $x - a reference to $array1[1] - somehow intertwined with $array2??

That initial question was followed-up by another one: "Assign by reference bug", and that has a good answer which explains theoretically what's going on. The key part is this:

The "shared" data stays shared, with the initial assignment just acting as an alias. It's not until you start manipulating the arrays with independent operations like ...[] = ... that the intepreter starts to treat them as divergent lists, and even then the shared data stays shared so you can have two arrays with a shared first n elements but divergent subsequent data.

In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.

OK, cool, I believe it. However I wanted to see it in action.

My first hurdle here was getting a lucid/helpful answer to "PHP determine if a variable is a reference". Most answers I spotted initially were "no, can't be done. Why even would you want to?" (but not in a "let's see if there's another approach" sense, but in a "I'm getting defensive about PHP" sense).

However that's not strictly true, as it turns out. I switched my googling to "PHP reference count", and the first link got me pointed in the direction of XDebug, which enables one to inspect reference counts and stuff like that.

When $numbers is first created, it's refcount is 1 (itself), and its is_ref is false. Fair enough.

When $refToSecondElement is made, $numbersrefcount and is_ref don't change, but note that its second element now has a refcount of two (itself and $refToSecondElement), and it not states is_ref=1. So not only is $refToSecondElement a reference, so is $numbers[1].

When $copyOfNumbers is made, initially it's just a reference. Note the refcount on both reflects this, but the is_ref does not. I guess this is because there's a difference between how PHP handles value copying internally is distinct from actively creating references. Not sure.

Also note that the refcount on $refToSecondElement (and $numbers[1] and $copyOfNumbers[1]) still reflect two references. As $copyOfNumbers is itself a reference at this point, $numbers[1] and $copyOfNumbers[1] are exactly the same thing. Not a new reference.

Now $copyOfNumbers[1] changes, so PHP has to actually make $copyOfNumbers a copy now, not just a reference back to $numbers. This is an example of copy-on-write, and is a performance optimisation. If all one is doing is reading a copied value, then leaving it as a reference is fine. It's not until the copied data changes that it needs to take on life of its own. And this is borne out by the fact that $numbers and $copyOfNumbers now have a refcount of one apiece.

On the other hand, the refcount on $numbers[1], $refToSecondElement and $copyOfNumbers[1] is now three, because $copyOfNumbers[1] is now a separate reference from $numbers[1] .

Also note that - as one would expect - the value change to $copyOfNumbers[1] is reflected in $referenceToSecondElement too. And indeed back to the reference in $numbers[1] too. This latter bit is the thing we didn't initially expect, but it kinda makes sense now.

Lastly we change $copyOfNumbers[2], and we see the refcount for it and $numbers[2] decrement, because again we are seeing copy-on-write: PHP has ceased using a reference to $numbers[2] for $copyOfNumbers[2], and it's now its own value.

XDebug came in handy here demonstrating what's going on, and being able to see how the values / references change as values are changed.

I feel just that slight bit less ignorant about how PHP works now. Nice one.

Search This Blog

About

I've been a web developer since 2001. I have spent 13yrs as a CFML developer, and until Sept 2014, that was the main subject matter here. I've now been re-tasked as a PHP developer, so learning PHP will become the focus of this blog. But I also mess around with other languages too.

I tend to be a bit "forthright" in my opinions, I am indelicate, and I tend to swear too much. This will come out occasionally here: I make no apology for it.

Everything said here is my own opinion. Feel free to disagree with me :-)