I have to admit, I’ve been working quite happily in PHP for years without ever really needing to use a pointer. Nevertheless, eventually you’ll come across a problem that just calls out for a pointer, as I did the other day. This is when I discovered that PHP does not actually have pointers.
If you’ve ever seen an “&” symbol in PHP code, you could certainly be excused for confusing it with a pointer. Here’s an example of “&”:
1
2
3
4
| $a = "Hello";
$b = &$a;
echo $b;
//> Hello |
It sure looks like $b contains a pointer to $a, right? In reality, the “&” symbol invokes a peculiar PHP construct called a reference, which unfortunately works an awful lot like a pointer without actually being one. In addition, “&” isn’t really an operator, at least not in this context. PHP is just letting us be lazy and mess up what should look like this: “$b =& $a”. The operator is “=&”.
Semantics aside, let’s look at an example where treating a reference like a pointer can mean big trouble:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| $a = "Hello";
$b =& $a;
echo "$b\n";
$a = "Hello, World!!\n";
for ($b = 5; $b > 0; $b--) {
echo "$b...\n";
}
echo $a;
//> Hello
//> 5...
//> 4...
//> 3...
//> 2...
//> 1...
//> 0 |
What’s going on here? Most of the code worked OK, but when we went to print out the last line, “Hello World!!” had been replaced with the number 0. If you look back at the code, you’ll see that the for loop ends up setting $b to 0, and somehow our “pointer” is working backwards, putting the value we assigned to $b back into $a. This is because references do not work like pointers.
So how do references work, then? The PHP manual describes references as being like a hard-link in a unix filesystem. This is a very accurate description, but just in case your unix filesystem knowledge is a little rusty, let me pose an alternate explanation.
Imagine that everything in PHP that starts with a $ sign is actually a pointer, that whenever used is magically dereferenced to take you to the memory where it is stored. I have no idea if this is actually what happens, but it makes a good model. PHP gives you no way to manipulate these pointers, other than using the “=&” operator (or unset, more on that later). Doing “$a =& $b;” causes the pointer value for $a to be replaced with the pointer value for $b. From now on, and $a and $b both point to the same bit of memory. This explains what happened in the previous example. $a and $b both point to the same thing, so when we used $b in our for loop, it overwrote the value in $a.
If we want to change where it is that $a points, instead of changing the value that is stored there, we have only two options: use another “=&” to point $a somewhere else, or use the unset function to get rid of the pointer for $a altogether, leaving our symbol “$a” free to be used for a brand new value in a shiny new memory location.
Now we’ve clarified how references work, I want to warn you that they can potentially be very dangerous, especially when used in the global scope. Consider the second paragraph from our last example:
4
5
6
7
8
| $a = "Hello, World!!\n";
for ($b = 5; $b > 0; $b--) {
echo "$b...\n";
}
echo $a; |
If just look at this code alone, there’s no reason it shouldn’t work. The reference we created in the first part of this example destroyed the sanity of our coding environment. Once you’ve created a reference, there’s really no practical way to look at some other piece of code in the same scope and know that it will work. Thankfully we can avoid much of these problems by using functions or methods to keep our scope separate, however if you do end up setting a reference on a global variable, it’s very important that you clear it up afterwards using the unset command.
Here’s our example with the unset function added in to clean up our reference:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| $a = "Hello";
$b =& $a;
echo "$b\n";
unset($b); //<-- added
$a = "Hello, World!!\n";
for ($b = 5; $b > 0; $b--) {
echo "$b...\n";
}
echo $a;
//> Hello
//> 5...
//> 4...
//> 3...
//> 2...
//> 1...
//> Hello World!! |
And our function now works as intended.