The Magic is in the Methods
Submitted by Matthew Turland on Tue, 02/10/2009 - 12:13So my good friend Brandon Savage happened to mention that he has yet to see a really good guide for the magic methods introduced in PHP 5. Being that I'm always looking for good topics to blog about, I thought I'd take this week's post as an opportunity to oblige him.
To identify declarations for methods that are magic, look for a prefix of two underscores (__) in the method name. This is a convention used consistently in all existing magic methods and that will continue to be used for any that are added in the future, such as the __callStatic and __invoke methods being added in PHP 5.3. (Side note: It's recommended that this convention not be used in naming your own methods for that very reason.)
You're probably asking, "What makes magic methods so magic?" The name "magic methods" probably stems from the term "magic" as it's applied to programming. More specifically, it refers to the fact that the PHP interpreter invokes magic methods implicitly in response to specific events, versus the normal behavior of invoking them because the developer explicitly calls them by name. To explain magic methods, I'll examine them from the perspective of the events that cause them to be called.
An excellent way to see these events in action is by using Xdebug tracing features to observe when magic methods are called. I've done so in this blog post using the following Xdebug configuration settings.
xdebug.auto_trace=1 xdebug.show_exception_trace=1 xdebug.trace_output_dir=/home/matt/Desktop/xdebug xdebug.collect_params=4 xdebug.collect_vars=1
Setting Up and Tearing Down
Two of these methods that you may already be familiar with are __construct and (less likely) __destruct.
__construct implements the constructor concept in PHP and actually existed in versions of PHP prior to 5, except that its name wasn't constant as it is in 5. Instead, it took on the name of the class in which it was declared, a behavior which is deprecated but still supported in 5.
When an object is instantiated using the new keyword, __construct is invoked. Its purpose is to enable any initialization that may be necessary when an instance of the class is created. Note in the trace below how line 9 (the assignment of $foo) invokes __construct.
Code:
<?php
class foo {
public function __construct() {
}
}
$foo = new foo;
Trace:
-> {main}() test.php:0
-> foo->__construct() test.php:9
Likewise, __destruct implements the destructor concept and is executed when an object is either explicitly unset or (more often) simply goes out of scope. Unlike __construct, the line number for __destruct may be unknown (hence the 0) since it's possible for garbage collection of objects to be delayed until the script has terminated.
Code:
<?php
class foo {
public function __destruct() {
}
}
$foo = new foo;
Trace:
-> {main}() test.php:0
-> foo->__destruct() test.php:0
Virtual Methods
Normally, if you call an instance method (as opposed to a static method) on an object and the class of that object does not define that method, it results in an error that looks like this: "Fatal error: Call to undefined method foo::bar()." Before it resorts to issuing this error, the PHP interpreter will first check the class to see if it declares __call. If so, it will invoke __call instead of issuing the error. Note in the example below that the parameters passed to __call reflect the undeclared method that was called and the actual parameters passed when the call is issued.
Code:
<?php
class foo {
public function __call($name, array $arguments) {
}
}
$foo = new foo;
$foo->bar('baz', 42);
Trace:
-> {main}() test.php:0
-> foo->bar('baz', 42) test.php:10
-> foo->__call($name = 'bar', $arguments = array (0 => 'baz', 1 => 42))
test.php:0
__call is limited to instance methods. For equivalent functionality for static methods, check out __callStatic. It has the same parameters and, aside from being limited to static methods, performs in the same way. As mentioned earlier, it is only available in PHP 5.3 and up.
Code:
<?php
class foo {
public static function __callStatic($name, array $arguments) {
}
}
foo::bar();
Trace:
-> {main}() test.php:0
-> foo::bar() test.php:8
-> foo::__callStatic($name = 'bar', $arguments = array ())
test.php:0
Virtual Properties
Also known as getters and setters, methods to return and set the value of an instance property are fairly common. __get and __set are similar to __call in that they are called if a property is not explicitly declared within a class.
Code:
<?php
class foo {
public $bar = 42;
public function __get($name) {
return "Called __get for $name";
}
}
$foo = new foo;
echo $foo->bar, PHP_EOL;
echo $foo->baz, PHP_EOL;
Output:
42
Called __get for baz
Trace:
-> {main}() test.php:0
-> foo->__get($name = 'baz') test.php:0
Another case in which __get and __set are invoked is when the referenced property is not visible to the scope in which the get or set operation is taking place. See below for an example of that.
Code:
<?php
class foo {
protected $bar = 42;
public function __get($name) {
return "Called __get for $name";
}
}
$foo = new foo;
echo $foo->bar, PHP_EOL;
echo $foo->baz, PHP_EOL;
Output:
Called __get for bar
Called __get for baz
Trace:
-> {main}() test.php:0
-> foo->__get($name = 'bar') test.php:0
-> foo->__get($name = 'baz') test.php:0
Conversely, __call and __callStatic do not behave this way; attempts to use them to circumvent visibility (such as calling a non-public method outside the class or calling a private method in a parent class from a subclass) result in fatal errors.
At this point, you might be wondering how calls to isset, empty, and unset are handled. More magic methods, of course! Check out __isset and __unset. Using these, you can implement the logic to deal with these events as appropriate for your particular application.
Code:
<?php
class foo {
protected $data = array();
public function __get($name) {
return $this->data[$name];
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
$foo = new foo;
$foo->bar = 42; // calls __set
var_dump($foo->bar); // calls __get, outputs int(42)
var_dump(isset($foo->bar)); // calls __isset, outputs bool(true)
unset($foo->bar); // calls __unset
Trace:
-> {main}() test.php:0
-> foo->__set($name = 'bar', $value = 42) test.php:0
-> foo->__get($name = 'bar') test.php:0
-> var_dump(42) test.php:21
-> foo->__isset($name = 'bar') test.php:0
-> var_dump(TRUE) test.php:22
-> foo->__unset($name = 'bar') test.php:0
Object Storage
It's also possible to hook into the process of serializing objects using __sleep and __wakeup. __sleep can be used to perform any tasks that may be necessary before calling serialize on an object, as well as to specify which properties in an object should be included in the serialized string (which is handy for large objects) by returning an array of property names. Its counterpart __wakeup performs any reinitialization tasks that may be necessary when unserialize is called.
Code:
<?php
class foo {
private $bar = 42;
public function __sleep() {
return array_keys(get_object_vars($this));
}
public function __wakeup() {
}
}
$serialized = serialize(new foo);
$foo = unserialize($serialized);
Trace:
-> {main}() test.php:0
-> serialize(class foo { private $bar = 42 }) test.php:12
-> foo->__sleep() test.php:0
-> get_object_vars(class foo { private $bar = 42 }) test.php:6
-> array_keys(array ('bar' => 42)) test.php:6
-> unserialize('O:3:"foo":1:{s:8:"\000foo\000bar";i:42;}') test.php:13
-> foo->__wakeup() test.php:0
Another means to the same end involves using __set_state, which accepts an array of data and handles the process of instantiating an object using that data before returning the new instance. The var_export function accepts an object and returns a string containing PHP code that calls __set_state to generate the given instance of that object.
Code:
<?php
class foo {
public $bar = 42;
public static function __set_state($arguments) {
$obj = new self;
$obj->bar = $arguments['bar'];
return $obj;
}
}
$foo = new foo;
$exported = var_export($foo, true);
eval('$imported = ' . $exported . ';');
var_dump($imported);
Output:
object(foo)#2 (1) {
["bar"]=>
int(42)
}
Trace:
-> {main}() test.php:0
-> var_export(class foo { public $bar = 42 }, TRUE) test.php:13
-> eval('$imported = foo::__set_state(array(\n \'bar\' => 42,\n));')
test.php:14
-> foo::__set_state($arguments = array ('bar' => 42)) test.php(14) :
eval()'d code:3
-> var_dump(class foo { public $bar = 42 }) test.php:15
String Typecasting
Objects can be treated as strings through type juggling (implicit or explicit) and the magic method __toString. A few situations in which this occurs are passing the object directly to echo, concatenating it to something else, having it implicitly cast to a string via a function like sprintf, or explicitly casting it to a string.
Code:
<?php
class foo {
public function __toString() {
return 'Hello';
}
}
$foo = new foo;
echo $foo;
echo PHP_EOL, $foo, " world!";
echo PHP_EOL, sprintf("%s again, world!", $foo);
echo PHP_EOL;
var_dump((string) $foo);
Trace:
-> {main}() test.php:0
-> foo->__toString() test.php:0
-> foo->__toString() test.php:0
-> sprintf('%s again, world!', class foo { }) test.php:12
-> foo->__toString() test.php:0
-> foo->__toString() test.php:0
-> var_dump('Hello') test.php:14
Deep Cloning
Because of the way objects behave in PHP 5, when an object containing other objects is cloned, a shallow copy is created such that both instances of the object point to the same other objects. __clone is used to override this behavior in favor of another behavior, such as creating a deep copy where all internal objects are cloned as well. One slightly odd detail worth noting is that __clone is called on the original instance, not the new clone instance.
Code:
<?php
class foo {
public $x = 42;
}
class bar {
public $foo;
public function __construct() {
$this->foo = new foo;
}
}
class baz {
public $foo;
public function __construct() {
$this->foo = new foo;
}
public function __clone() {
$this->foo = clone $this->foo;
}
}
echo 'Before (shallow)', PHP_EOL;
$bar1 = new bar;
$bar2 = clone $bar1;
var_dump($bar1, $bar2);
echo 'After (shallow)', PHP_EOL;
$bar1->foo->x = 43;
var_dump($bar1, $bar2);
echo 'Before (deep)', PHP_EOL;
$baz1 = new baz;
$baz2 = clone $baz1;
var_dump($baz1, $baz2);
echo 'After (deep)', PHP_EOL;
$baz1->foo->x = 43;
var_dump($baz1, $baz2);
Output:
Before (shallow)
object(bar)#1 (1) {
["foo"]=>
object(foo)#2 (1) {
["x"]=>
int(42)
}
}
object(bar)#3 (1) {
["foo"]=>
object(foo)#2 (1) {
["x"]=>
int(42)
}
}
After (shallow)
object(bar)#1 (1) {
["foo"]=>
object(foo)#2 (1) {
["x"]=>
int(43)
}
}
object(bar)#3 (1) {
["foo"]=>
object(foo)#2 (1) {
["x"]=>
int(43)
}
}
Before (deep)
object(baz)#4 (1) {
["foo"]=>
object(foo)#5 (1) {
["x"]=>
int(42)
}
}
object(baz)#6 (1) {
["foo"]=>
object(foo)#7 (1) {
["x"]=>
int(42)
}
}
After (deep)
object(baz)#4 (1) {
["foo"]=>
object(foo)#5 (1) {
["x"]=>
int(43)
}
}
object(baz)#6 (1) {
["foo"]=>
object(foo)#7 (1) {
["x"]=>
int(42)
}
}
Trace:
-> {main}() test.php:0
-> bar->__construct() test.php:25
-> var_dump(class bar { public $foo = class foo { public $x = 42 } },
class bar { public $foo = class foo { public $x = 42 } }) test.php:27
-> var_dump(class bar { public $foo = class foo { public $x = 43 } },
class bar { public $foo = class foo { public $x = 43 } }) test.php:31
-> baz->__construct() test.php:34
-> baz->__clone() test.php:0
-> var_dump(class baz { public $foo = class foo { public $x = 42 } },
class baz { public $foo = class foo { public $x = 42 } }) test.php:36
-> var_dump(class baz { public $foo = class foo { public $x = 43 } },
class baz { public $foo = class foo { public $x = 42 } }) test.php:40
This example is rather long, so here's the play-by-play.
- Three classes are created: a data structure class foo, a class bar that uses the normal cloning behavior of creating a shallow copy, and a class baz that overrides the normal cloning behavior by cloning the internal instance of the foo class (thus resulting in a deep clone).
- An instance of bar is created and called $bar1, then a clone $bar2 is created. Note that the different object identifiers in the var_dump output do indicate these to be two different / separate instances. Also note that the object identifier of the internal foo object is the same for both classes.
- The internal $x property of $foo is changed in $bar1. A var_dump of both $bar1 and $bar2 reveals that $x has changed for both, again because both point to the same foo object.
- An instance of baz is created and called $baz1, then a clone $baz2 is created. The clone operation results in baz::__clone() being invoked. Again, different object identifiers in var_dump output indicate them to be two separate objects. The object identifiers of the internal foo objects are also different this time, indicating that they are separate objects as well.
- The internal $x property of $foo is changed in $baz1. A var_dump of both $baz1 and $baz2 reveals that $x has changed for $baz1 but not $baz2, again because their $foo properties point to separate instances of the foo class.
Lambda Emulation
A new feature in PHP 5.3 is lambda functions, also called anonymous functions. How does this relate to magic methods? Just as objects can be treated as strings, they can also be treated as lambda functions via the magic method __invoke. One implication of this is that the is_callable function will return true for such an object. Unlike __call, __invoke receives exactly what's passed to it when the object is invoked as a lambda function, so declare the parameter list for __invoke appropriately as you would for any other function. (For example, if you want a variable-length parameter list, you can use the func_get_args function as you would normally.)
Code:
<?php
class foo {
public function __invoke() {
}
}
class bar {
public function __invoke($x) {
}
}
class baz {
public function __invoke($x, $y) {
}
}
$foo = new foo;
$foo();
$bar = new bar;
$bar(42);
$baz = new baz;
$baz(42, 'bay');
Trace:
-> {main}() test.php:0
-> foo->__invoke() test.php:19
-> bar->__invoke($x = 42) test.php:22
-> baz->__invoke($x = 42, $y = 'bay') test.php:25
Wrapping Up
Hopefully this post has given you insight into how magic methods work. At some point in the future, I may follow it up with a post on a few potential uses for them. If you have any questions or other discussion, feel free to leave a comment. Thanks for reading!

