While it makes sense for PHP beginners to first learn that objects are passed by reference rather than focus on technical details they need not comprehend yet, it worries me how many experienced developers still hold this belief.
The basic example used to justify said misconception is the following:
1 2 3 4 5 6 7 8 9 10 11 12 |
class MyClass { public $prop = 'foo'; } function func($arg) { $arg->prop = 'bar'; } $object = new MyClass; echo $object->prop; // prints 'foo' func($object); echo $object->prop; // prints 'bar' |
The function func
has its sole argument passed by value, yet it somehow changed the state of $object
. Is that not what passing by reference does?
Well, not exactly. Consider the following, where the object is replaced with a new one inside the function:
1 2 3 4 5 6 7 8 9 |
function func($arg) { $arg = new MyClass; $arg->prop = 'bar'; } $object = new MyClass; echo $object->prop; // prints 'foo' func($object); echo $object->prop; // still prints 'foo' |
Here, $object
remains unchanged. However, if we add a reference:
1 2 3 4 5 6 7 8 9 |
function func(&$arg) { $arg = new MyClass; $arg->prop = 'bar'; } $object = new MyClass; echo $object->prop; // prints 'foo' func($object); echo $object->prop; // prints 'bar' again |
We now revert to the original behavior.
Now it has been shown that references do make a difference for objects, the best way to understand why is to clearly define both.
A reference, first, is nothing but an alias, which means that both variables point to the same location in memory and can therefore be used interchangeably.
An object variable, on the other hand, doesn’t contain the object itself but an identifier that allows the runtime to access its content. In fact, it works exactly like pointers in C++, and therefore explains PHP’s use of the same syntax as the ‘member of pointer’ operator (->
), which translates to ‘point to the given property for the object of given identifier’.
What happens in the first example is that this identifier is passed by value and copied to $arg
in the function scope. Then, the runtime is asked to modify the property prop
of the object corresponding to the identifier. The variable $object
in the global scope was never modified, but the object it points to was.
In the second example, the identifier is once again passed by value, but then, the local variable $arg
is overridden with another identifier to another object, and it is the property prop
of that second object that is modified, explaining why the original one remains unchanged.
The third example, finally, has the identifier passed by reference to func
. A second object is once again created, and its identifier is saved in the alias $arg
, which is, by definition, just another way to refer to the variable $object
in the global scope, which in turn now points to a new, modified object.
Back to pointers to objects in C++, the code below shows how they can be used to obtain the exact same behavior as PHP objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <string> using namespace std; class MyClass { public: MyClass() { this->prop = "foo"; } string prop; }; void func1(MyClass* arg) { arg = new MyClass; arg->prop = "bar"; } void func2(MyClass* &arg) { arg = new MyClass; arg->prop = "bar"; } int main() { MyClass* object1 = new MyClass; cout << object1->prop << endl; // prints 'foo' func1(object1); cout << object1->prop << endl; // still prints 'foo' MyClass* object2 = new MyClass; cout << object2->prop << endl; // prints 'foo' func2(object2); cout << object2->prop << endl; // prints 'bar' again return 0; } |
Though, if you know how pointers work, you probably didn’t need to read this article anyway.
In C#, the behavior is once again the same, but the introduction of reference types (variables that store references to their data, as opposed to directly containing the data) abstracts the concept of pointers and allows us to use objects directly, and with the ‘member of’ operator (.
) only:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; class MyClass { public string prop = "foo"; } class Program { static void func1(MyClass arg) { arg = new MyClass(); arg.prop = "bar"; } static void func2(ref MyClass arg) { arg = new MyClass(); arg.prop = "bar"; } static void Main(string[] args) { MyClass object1 = new MyClass(); Console.WriteLine(object1.prop); // prints 'foo' func1(object1); Console.WriteLine(object1.prop); // still prints 'foo' MyClass object2 = new MyClass(); Console.WriteLine(object2.prop); // prints 'foo' func2(ref object2); Console.WriteLine(object2.prop); // prints 'bar' again } } |
Please note that C# actually supports pointers (to value types and other pointer types), though they should only be used to interact with native functions or in performance-critical code.