SiteCrafting, Inc.

19 Sep
PHP and the __Magical Chamber of Secrets

PHP and the __Magical Chamber of Secrets

What?

Believe it or not, magic does have a place in PHP. In fact, you have probably already wielded some PHP magic in your code. Think back, long and hard, and try to remember if you have ever written a class method starting with two underscores. Go ahead, I'll wait...

If you've already written a class and utilized the __construct() method, consider yourself a budding PHP magician. However, it is but one of many magic methods that PHP offers. In general, magic methods in PHP are special methods (prefixed by a double underscore, __) that are triggered in some indirect way, instead of being invoked directly. For example, you have probably never seen the constructer used like this: $obj->__construct()

__construct() & __destruct()

Casually known as the constructor and destructor, these two methods are the bookends to a class.

The __construct() method is triggered every time an object is instantiated. This makes for an excellent place to run any initialization or setup tasks for a given class. This is by far the most common magic method in PHP.

The __destruct() method is the lesser used sibling that, as you may have guessed, is triggered when PHP has determined a particular instance will no longer be used. This is where you would want to sweep that mess you made elsewhere in the class under the rug (I promise, I won't tell anyone).

__get()

Commonly known as an accessor, this method allows the author of the class to intercept requests for properties and decide how and if they are returned. Simply attempting to retrieve an inaccessible property (because it either doesn't exist, or it is defined as private or protected) triggers this method.

/* Defined somewhere in your class... */ public function __get($name) { switch($name) { case “username”: return $this->data[“un”]; case “age”: return $this->calcAge(); } } /* ... meanwhile, outside your class... */ $user = new User(); // These both trigger the same code: echo $user->username; echo $user->__get(“username”);

__set()

A counterpart to the __get() accessor method, this handles the other direction: requests to assign values to inaccessible properties.

/* Defined somewhere in your class... */ public function __set($name, $value) { switch($name) { case “username”: $this->data[“un”] = strtolower($value); } } /* ... meanwhile, outside your class... */ $user = new User(); // These both trigger the same code: $user->username = “GeorgeJetson”; $user->__set(“username”, “GeorgeJetson”);

__isset()

This method is triggered whenever isset() is called on a property of the class. This allows you to define some custom logic for determining whether or not a property is "set." Perhaps it should only be considered "set" if it contains non-default user data.

/* Defined somewhere in your class... */ public function __isset($name) { switch($name) { case “username”: return (isset($this->data[“un”]) && $this->data[“un”] != “default”); } } /* ... meanwhile, outside your class... */ $user = new User(); // Outputs "Username not found!"; echo isset($user->username) ? 'Username is set!' : 'Username not found!'; // Outputs "Username not found!"; $user->username = 'default'; echo isset($user->username) ? 'Username is set!' : 'Username not found!'; // Outputs "Username is set!"; $user->username = "GeorgeJetson"; echo isset($user->username) ? 'Username is set!' : 'Username not found!';

__unset()

Like its counterpart, __isset(), this method allows you to specify what actually happens when an property is "unset." Rather than killing the property, you might want to simply reset it to a default value:

/* Defined somewhere in your class... */ public function __unset($name) { switch($name) { case “username”: $this->data[“un”] = “default”; } } /* ... meanwhile, outside your class... */ // Outputs "georgejetson" echo $user->username; // Outputs "default" unset($user->username); echo $user->username;

__call() & __callStatic()

Thus far we have seen how the author of a class can control how properties are accessed and manipulated. As it turns out, class methods have also been invited to that party (both in the instance and static contexts):

/* Defined somewhere in your class... */ public function __call($name, $args) { if(substr($name, 0, 3) == “say”) { echo $this->data[“un”] . “ says:”; echo substr($name, 3); } } public static function __call($name, $args) { if(substr($name, 0, 3) == “say”) { echo substr($name, 3); } } /* ... meanwhile, outside your class... */ // Outputs "georgejetson says Hello" $user->sayHello(); // Outputs "HelloThere" User::sayHelloThere();

__toString()

This magic method provides a straightforward and intuitive way to convert an object to a string. It allows the class author to define exactly what that string looks like:

/* Defined somewhere in your class... */ public function __toString() { echo “
    ”; foreach($this->data as $key => $val) { echo “
  • ”; echo $key . “: “ . $val; echo “
  • ”; } echo “
”; } /* ... meanwhile, outside the class... */ // Implicit Cast $userObject = new User(); echo $userObject; // Explicit Cast $userString = (string) $userObject; echo $userString; /* Both would output something like this:
  • id: 1337
  • username: georgejetson
  • email: gjetson@spacelysprockets.com
  • status: employed
*/

__clone()

To understand the utility behind the __clone() magic method, let's first look at what it means to clone an object. By default, PHP passes objects by reference. This means that by assigning an object to one variable, and then assigning that variable to another, you are creating two variables that point to the same object.

$userAlt = $user; $user->username = “FredFlintstone”; // Outputs: "fredflintstone" echo $userAlt->username;

If you wanted two completely independent instances instead, you would use the clone keyword.

$userAlt = clone $user; $user->username = “FredFlintstone”; // Outputs: "default" echo $userAlt->username;

By default, the entire object and its properties are copied over to the new one. The __clone() magic method allows you to define something different. Perhaps not everything should be copied, or maybe somethings should be reset to their default values. This magic method leaves it entirely up to you.

/* Defined somewhere in your class... */ public function __clone() { $this->data[“id”]++; $this->data[“un”] .= “_cloned”; } /* ... meanwhile, outside the class... */ $newUser = clone $user; echo $user->id . “ >> ” . $newUser->id; echo $user->username . “ >> ” . $newUser->username; /* 1337 >> 1338 georgejetson >> georgejetson_cloned */

Conclusion

At this point you should be well-versed some of the most commonly used magic methods PHP has to offer. However, every new release of PHP seems to add another magic method to the list, so don't be shy and go explore what else is out there. If you have found some clever uses for all this magic, share it in the comments.

Coding Techniques, PHP, Software Engineering
by Nick Williams | 9/19/2011 3:23pm | Comments (0)

No comments found.


Leave a Comment




* required    Comment Guidelines