Firehed's Blog

Cheating with PHP's __set magic method

So, I’ve been following along Stanford’s iPhone Development class on iTunes U (iTunes link), and something covered early is how Objective-C allows you to use “dot notation” for getters and setters provided you follow naming conventions: object.propertyName runs the [object propertyName] getter, and object.propertyName = someValue runs the [object setPropertyName: someValue] setter. Tricky, eh?

I wanted something similar in my PHP calls. Of course it’ll be “arrow notation” rather than “dot notation”, but the same concept applies. Naturally, if you have public properties in your object it’s a non-issue, but if you’re like me, all of your properties have a protected or private visibility and are only accessible through getter and setter methods. The reason to do so in my case is very important - my abstracted save() method only makes a database call if $this->hasBeenChanged is true (to prevent unnecessary DB calls, among other things), and all of my setters call the appropriately named $this->markAsChanged() method. If I were to set the values directly through public visibility, this wouldn’t work at all.

But there’s a trick: PHP’s magic methods for getters and setters, __get($var) and __set($var, $value). Here’s where the naming convention thing comes into play: you can use $var passed to the magic methods in order to run the setters through a little string concatenation.

public function __set($variable, $value) {
if (method_exists($this, $method = 'set' . $variable)) {
$this->$method($value);
}
} // function __set

What this does is that when you call $object->someProperty = 'someValue';, PHP first checks to see whether $someProperty exists in the object as a public member, and if so will set the value. Since it’s not, it will call __set('someProperty', 'someValue'); if you have it defined. What we’ve done is define it in such a way that it will attempt to call $object->setsomeProperty('someValue'); if it exists (PHP’s function and method calls are not case sensitive; I do, however, use upperCamelCase for my naming conventions. Thankfully, the lack of case sensitivity here allows us to not worry about capitalizing the first letter of the method name). To shorten things somewhat, I assign a value to $method right in the method_exists() call, since you’re not allowed to run $this->set$variable($value); - you’ll get a fatal error at runtime.

I keep my __get($var) method a little more simple, since I (at least in this project) don’t care if you read the values at random; only writing could be problematic if done wrong. So I keep it as simple as possible here:

public function __get($variable) {
return isset($this->$variable) ? $this->$variable : null;
} // function __get

A little ternary notation combined with the isset() function gets the job done quite easily. However, if you have explicit getter methods, there’s no reason you couldn’t modifiy the above __get($var) code accordingly:

// Objective-C [object property]/[object setProperty:value] notation
public function __get($var) {
if (method_exists($this, $var)) {
return $this->$var();
}
return null; // Or whatever you want the default to be if the property doesn't exist; you could throw an exception here if you prefer.
} // function __get

// My own $obj->getProperty()/$obj->setProperty($value) notation:
public function __get($var) {
if (method_exists($this, $method = 'get' . $var)) {
return $this->$method();
}
return null; // Again, set "fail" value/action to taste
} // function __get