So you’ve been working in Python for a year and then along comes a PHP gig
that you just can’t pass up. All of a sudden you’re realizing just how much
you miss some of the familiar Pythonisms you’ve come to rely on. One such
feature that is lacking in PHP is the concept of descriptors. Fortunately,
it’s possible to pretty closely simulate their behaviour in PHP with just
a few special interfaces and classes.

Mind Blowing

For the uninitiated, there are a fewgreatarticles online that go
into great detail about what Python descriptors are, and what they can do.
Descriptors are a notoriously mind boggling topic that has left many a
curious developer cross-eyed and drooling, and usually more confused than
we he started. But they’re simple to understand, really: They’re just object
instances posing as dynamic properties which are defined on a class,
evaluated at runtime by a method on the descriptor object itself, that are
accessible through either an object instance, or statically through a
property on the the class itself!

Yeah. "Simple."

To be honest, most of the difficulty in understanding descriptors stems
from the fact that (especially in PHP,) classes aren’t really thought of as
"objects" much. Classes in general are a little bit "quirky" in PHP, being
referenced as a quoted class name string half the time, an unquoted global
class name the other half, and as a special keyword the other-other half!
Only now are the more advanced class related tools even appearing in the PHP
world, and they’re still not complete (which will really screw us later in this
article). Plus there’s just not that much information about down and dirty
class-level manipulation for PHP out there. Where the official documentation
is lacking, it’s only supplemented by helpless user comments flailing wildly
trying to understand the murky examples. It’s a real shame because ultimately,
we as developers fall victim to the catch 22 of PHP’s evolution as a language:
Features will be added after widespread adoption of the feature." Progress
happens very slowly because developers lack the understanding (or perhaps
the vision) to take advantage of some of these features – much less
successfully lobby for improvements in the language itself. Regardless,
I’ll admit I never really even thought of classes in such a dynamic way
until I switched to Python. So, if you’ve never been exposed to Python and
its very object-like class syntax, then it’s going to be an even longer
road to enlightenment! No worries though, let’s just break it down slowly
and see what descriptors really look like: Descriptors are…

object instances…

Ok, so that means we’re dealing with an instance of a class, which will
obviously require a class definition. That’s a pretty simple concept –
define a class and create an instance of it. Somewhere along the line,
we’re going to have to define some kind of "descriptor" class, and
instantiate it. No big deal. With that in mind, let’s move on: Descriptors
are object instances…

posing as dynamic properties…

Dynamic properties are a slightly advanced concept, and usually make use of
the __getmagic method (and friends), which PHP automatically calls
when you try to access a non-existant property (member variable) on an object
instance. For example, in the following code, $obj will be an instance of
TheClass and the_property will be normal, non-dynamic instance
property:

The property is accessed as $obj->the_property. The following call will
issue a Notice level error by default, since we attempt to access
an undeclared property on $obj:

<?php// Access a nonexistent property.echo$obj->bogus_property;

However, if the class defines an instance method named __get, it can
intercept requests for undefined properties, and return a dynamic value.
That means we can calculate a value at runtime for what the world will
think is a normal instance property. The end product looks something like this:

<?php// Define the class.classDynamicClass{/** * Returns the value of a dynamic property. * * This method will issue a 'Notice' level error if a nonexistent * property is requested. * * @param string $name The name of the property to get. * * @return mixed */publicfunction__get($name){// Check the name of the requested property.if($name=='the_property'){// Return the dynamic value for 'the_property' property.returntime();}// Trigger an error if no property was found with the name.trigger_error(sprintf('Undefined property: %s::$%s',get_class($this),$name));}}// Create the object instance.$obj=newDynamicClass();// Access the object instance's property.echo'The property: '.$obj->the_property;

Now every time we access the $obj->the_property property, the __get
method will be evaluated, and – specifically – the current time will be
returned.

That’s really all there is to the concept of dynamic properties. They’re
incredibly convenient though, and indispensable when you want to define
a clean and easy to understand API or standard interface to some functionality.
There are in fact even more magic methods, allowing you to control property
access in contexts other than "getting." We’ll deal with a few of them later.

Let’s get back to unraveling the mystery of descriptors. We know that they’re
object instances (duh), and that they themselves somehow pose as dynamic
properties. That means that, similarly to the __get method we’ve just
seen, descriptors handle access requests for undefined properties.
It’s not clear how an object instance actually does this amazing feat yet,
but let’s take it one step at a time: Descriptors are object instances
posing as dynamic properties…

which are defined on a class…

Holy crap, say what? Defined on a class? How do you define a property on
a class? Well, in the biz, we call a property defined on a class, a static
property. "Class members", "class properties", "static properties" – it’s all
the same. In fact, the PHP documentation can’t even make up its mind what
to call them. The premise is simple though. Remember that in PHP, classes are
defined globally. It might help to think of the class definition itself as
just a block of sourcecode that creates a single global instance of a "class"
type object the first time it is run. These global "class" object instances
have their own properties and methods, plus they know the how to do the
voodoo (that they do) to create a local instance of the class they represent
(so well). To declare a static class property, you just use the static
keyword in the class definition, and that property will now only be accessible
through the class itself and not through instances. The scope resolution
operator can be used to get the static property’s value:

<?phpclassStaticPropertyClass{// Declare the static property.publicstatic$the_static_property;// Declare the property.public$the_property;}// Create the object instance.$obj=newStaticPropertyClass();// Access the object instance's property.echo'The property: '.$obj->the_property;// Access the static class property.echo'The class property: '.StaticPropertyClass::$the_static_property;// This will produce an error.echo$obj->the_static_property;// And so will this.echoStaticPropertyClass::$the_property;

As you can see, class properties are completely separate from (and oblivious
to) instance properties. They represent a unique global value, associated
with the theoretical "global instance" of the "class" type object. Got it? Good.
To review, a descriptor is really just an object that happens to be stored
in a class property on some random class. It also will intercept and
specially handle requests for some sort of dynamic property, which we haven’t
really talked about yet. We do know that the value for the hypothetical
property will be…

evaluated at runtime by a method on the descriptor object itself…

That means that every time we try to get the value of the dynamic property,
the descriptor instance will run an instance method to determine the returned
value. So a descriptor has to define some kind of processing method, which
is loosely equivalent to the __get method we used earlier. The real
difference is that the PHP magic methods only automatically handle access
to dynamic properties on the exact instance on which the property is accessed.
The descriptor differs in that (somehow) it intercepts property access for
a different object, not itself.

Now our picture of a descriptor is complete, even though we don’t really
know how it’s going to work or what it’s for yet. According to what we
just discussed, we’re simply dealing with an instance of a class, with
a method that will calculate and return the dynamic value of the property
which the descriptor instance represents. And it goes a little something
like this:

<?php// Define the descriptor class.classDescriptorClass{/** * Calculates and returns the dynamic value for the property * associated with the descriptor instance. * * @return mixed */publicfunctiongetDescriptor(){// Do some special calculations.returntime();}}// Create a descriptor.$the_descriptor=newDescriptorClass();// Add the descriptor to the class.StaticPropertyClass::$the_static_property=$the_descriptor;

That’s it! Now $the_descriptor object fits all of the criteria of a
descriptor so far. It’s stored in a static class property
(StaticPropertyClass::$the_static_property), it’s an instance
of a class and it has an instance method called getDescriptor that
will return a dynamic value for some property somewhere. Ah yes – we have
to figure out how the descriptor relates to the property. Recall that
descriptors intercept requests for dynamic properties…

that are accessible through either an object instance…

Hold it right there! Forget the "either" for right now and let’s focus on
this part. The dynamic properties we’re going to be using with descriptors
are accessible through an object instance. So really, when we’ve got our
descriptor working correctly, we should be able to access a dynamic instance
property just like we did using __get earlier. The important thing to
note is that the name of the static class property in which the descriptor
is stored should be the name of the instance property we use to get the
dynamic value from the descriptor instance. I’ll say that again – store
a descriptor instance in a class property, use the name of that class
property to access the descriptor in an instance of that class. For
example, using the descriptor we stored in
StaticPropertyClass::$the_static_property, we would access the
descriptor’s calculated property value using $obj->the_static_property:

Every time we access $obj->the_static_property, we need
StaticPropertyClass::$the_static_property->getDescriptor() to run. Now
that we’re clear, we can actually go about implementing some interfaces and
a class that will take care of the actual property request handling. It’s
not as hard as it sounds! Let’s start by defining a DescriptorInterface
interface, which will need to be implemented by each descriptor class we
create.

<?php// An interface that all descriptors will implement.interfaceDescriptorInterface{/** * Returns the value of the descriptor when accessed. * * @param object $instance The object instance on which the descriptor was * accessed. 'null' will be passed if accessed the * descriptor was accessed statically. * @param string $owner The name of the class which owns the descriptor. * * @return mixed */publicfunctiongetDescriptor($instance,$owner);}

Notice that we added parameters to the getDescriptor method. These are
the parameters specified by the Python descriptor interface and they’ll
really come in handy later. For now just understand that the first
parameter is always the object instance whose descriptor property is being
accessed and the second parameter will always be the name of the class on
which the descriptor is defined. For example:

<?php// Define a descriptor.classChattyDescriptorimplementsDescriptorInterface{// Calculate the dynamic value.publicfunctiongetDescriptor($instance,$owner){returnsprintf("Hey %s, you're a %s.",$instance->name,$owner);}}// Define a simple class.classDude{public$name;publicstatic$descriptor;// Assign the name at creation.publicfunction__construct($name){$this->name=$name;}}// Add the descriptor instance to the class.Dude::$descriptor=newChattyDescriptor();// Create an object instance.$obj=newDude('Broseph');// Access the descriptor.echo$obj->descriptor;

The previous example will output Hey Broseph, You're a Dude. if
everything goes according to plan – which it won’t, until we sprinkle some
magic code dust on the Dude class. What we need Dude to take care
of is the hand-off from the dynamic property request to the descriptor’s
getDescriptor method. Since we’re definitely not going to want to add
extra code to every single class we use a descriptor with, let’s agree
that we’ll need a single base class from which all descriptor-bearing classes
will be extended. In this class, we want to intercept requests for undefined
instance properties, check for a descriptor with the same name in the static
class properties of the instance’s class, and then call the class property’s
getDescriptor method if we do in fact find a descriptor object. Here’s
one implementation of just such a base class:

<?php// Define a base class for objects that will use descriptors.abstractclassDescriptable{// Intercepts requests for nonexistent properties.publicfunction__get($name){// Get the name of this class.$class=get_class($this);// Get an introspection reflection for the class.$class_ref=newReflectionClass($class);// Check for a static class property with the given name.$attr=$class_ref->getStaticPropertyValue($name,null);// If the class property is an object, check for a descriptor.if(is_object($attr)){// Get an introspection reflection for the property.$attr_ref=newReflectionClass(get_class($attr));// Check to see if the static property is a descriptor.if($attr_ref->implementsInterface('DescriptorGet')){// Call the descriptor method and return the value.return$attr->getDescriptor($this,$class);}}// Trigger an error if no descriptor was found.trigger_error(sprintf('Undefined property: %s::$%s',$class,$name));}}

Subclasses of Descriptable will now correctly handle descriptors with no
further fiddling. PHP’s new reflection API (speaking of undocumented
features) is used to inspect the subclass and the descriptor instance to
determine whether descriptor handling should take place. Here’s a final,
working example that will correctly implement a descriptor according to the
Python rules we’ve been over so far:

<?php// Define a simple class.classWorkingDudeextendsDescriptable{public$name;publicstatic$descriptor;// Assign the name at creation.publicfunction__construct($name){$this->name=$name;}}// Add the descriptor instance to the class.WorkingDude::$descriptor=newChattyDescriptor();// Create an object instance.$obj=newWorkingDude('Brosephus');// Access the descriptor (works!)echo$obj->descriptor;

Brilliant! We’ve made a descriptor! The only thing we need to remember about
using the Descriptable class is that if we override the __get method,
we must call the __get method inherited from Descriptable. When
we call it in the overridden method will essentially effect the resolution
order of dynamic properties, so do tread lightly in these cases.

So far we’ve built a descriptor class that emulates Python’s descriptors in
all cases but one. Python descriptor properties may be accessed either
through an object instance (as we’ve just shown)…

or statically through a property on the the class itself!

And here’s where we hit the proverbial brick wall. Basically what we need is
a way to trigger a descriptor’s dynamic processing when accessing a static
class property. The really confusing part is that generally, the static
property will actually contain the descriptor instance itself! Mind Blow
part II, anyone? So every time we access a static class property that
contains a descriptor instance, we want to receive the dynamic value of
the descriptor, not the descriptor instance itself. If we could get that to
work, it would look like this:

<?php// Define a simple class.classUnemployedDudeextendsDescriptable{public$name;publicstatic$descriptor;// Assign the name at creation.publicfunction__construct($name){$this->name=$name;}}// Add the descriptor instance to the class.UnemployedDude::$descriptor=newChattyDescriptor();// Access the descriptor on the class (doesn't work!)echoUnemployedDude::$descriptor." You don't even exist, you lazy bum!";

Unfortunately, PHP has a problem. It provides no __get equivalent magic
method for static access. Try as we might, we’ll never get a dynamic value
for a class property (defined or otherwise). There has been great
discussion, a legendary bug ticket and even an RFC dealing with the
addition of a __getStatic magic method to the PHP core, but PHP 5.3
shipped without even a hint of progress in this area. Quel bummer, man! So
all static class properties must be declared in the class definition,
period. In fact, our descriptor implementation subtley relies on this
quirk. When __getStatic is finally available our descriptor class will
require that the class property be undefined in the class definition, and
assigned later. The assignment would take place in the __setStatic magic
method, which would be tasked with keeping track of added descriptors, most
likely in an array keyed off the property name for each descriptor. Yes, it
will be a brave new world for sure! Oh well, it looks like we were just
wasting our time on this descriptor pipe dream.

Not so fast! Don’t throw your keyboard in the trashcan yet – there is hope.
First, let’s examine precisely how big of a deal this one restriction on
our PHP descriptors is. Why would you even want static access to a dynamic
descriptor property? Coincidentally, you probably wouldn’t
want access to the dynamic value at all! In practice, descriptors rarely
define special handling for the class itself, rather they focus on manipulating
object instances. So the use cases are few in which you will even be dealing
with a descriptor value statically. What you’ll see far more often is
a descriptor returning its own instance instead of a value when it is
accessed as a static class property. If you think about it, this makes
total sense. How else are you supposed to ever get at the descriptor instance
otherwise? If UnemployedDude::$descriptor returned a value, there
would be no way to get at the descriptor instance at all, since that’s the
only way we know how to refer to the damn thing. This is just how PHP works
(which we’re stuck with) and it happens to correlate with the most likely
use case for a descriptor (luckily) so our descriptor class is still very
faithful to the Python equivalent.

One quick thing to note is that the dynamic accessor method required by the
descriptor interface accepts an instance parameter which will contain the
object instance whose descriptor property was requested. In a class descriptor
context, this parameter would always be null since there is no instance
in a static context.

Meet the Family

Up to this point, we’ve ignored a huge detail. In reality, "getting" isn’t
the only access method supported by Python descriptors. There is also support
for dynamic value "setting" and "deleting." These actions roughly correspond
to the PHP magic methods __set and __unset, and should be fairly self
explanatory. Descriptors intercept property "setting" and "unsetting" just as
they do for property "getting" currently. To fill out our descriptor
implementation, we should define some new interfaces to define how these new
methods will be called by the descriptable class.

<?php// The descriptor interface for "set" access.interfaceDescriptorSet{/** * Sets the value of the descriptor. * * @param object $instance The object instance on which the descriptor was * accessed. * @param mixed $value The value that was given to the descriptor. * * @return null */publicfunctionsetDescriptor($instance,$value);}// The descriptor interface for "unset" access.interfaceDescriptorUnset{/** * Unsets the descriptor's value. * * @param object $instance The object instance on which the descriptor was * accessed. * * @return null */publicfunctionunsetDescriptor($instance);}

The setDescriptor method will be called when setting the value of a
descriptor property like $obj->descriptor = 'new value'; and will receive
the new property value in the $value parameter. Note that the
unsetDescriptor method only receives the object instance as a parameter.
If you’re wondering what happened to the $class parameter that we used
in getDescriptor, good catch! It turns out, Python descriptors do
not allow static access for "setting" and "deleting (unsetting)" descriptor
properties. The reasoning is simple: if these methods were allowed for
static property access, you could never remove the descriptor instance from
the class. A full restart of the program would be required to empty the
static class property. That just isn’t practical, so the Python authors left
the feature out completely to prevent confusion. This is an extra bonus for
us, since we can’t use static class property access at all with descriptors
in PHP. That means we’re really only missing out on the one single use-case.

There is one other detail to descriptors that’s specific to PHP. Many PHP
developers use isset() to evaluate whether a property exists and is
valid. Unfortunately isset() will return false for any undeclared
property, even if we’ve overridden the __get method to return a value.
To accurately simulate a real property, we need to override the magic
__isset method to return true if a property is evaluated dynamically.
With this last piece of the puzzle, we are able to construct a robust
descriptable class, completing our PHP descriptor support:

<?php// The base descriptor interface.interfaceDescriptor{}// The descriptor interface for "get" access.interfaceDescriptorGetextendsDescriptor{/** * Returns the value of the descriptor when accessed. * * @param object $instance The object instance on which the descriptor was * accessed. 'null' will be passed if accessed the * descriptor was accessed statically. * @param string $owner The name of the class which owns the descriptor. * * @return mixed */publicfunctiongetDescriptor($instance,$owner);}// The descriptor interface for "set" access.interfaceDescriptorSetextendsDescriptor{/** * Sets the value of the descriptor. * * @param object $instance The object instance on which the descriptor was * accessed. * @param mixed $value The value that was given to the descriptor. * * @return null */publicfunctionsetDescriptor($instance,$value);}// The descriptor interface for "unset" access.interfaceDescriptorUnsetextendsDescriptor{/** * Unsets the descriptor's value. * * @param object $instance The object instance on which the descriptor was * accessed. * * @return null */publicfunctionunsetDescriptor($instance);}// The base class for all descriptor-bearing subclasses.abstractclassDescriptable{/** * Finds and returns a descriptor instance for the class. * * This method will return null if either a descriptor was not found in * the class property with the specified name, or if a descriptor was * found but does not implement the requested interface. Passing the * default interface name 'Descriptor' will return any type of descriptor * as long as it is stored in the correct class property. * * @param string $name The name of the descriptor property being accessed. * @param string $iface The name of the descriptor interface which must * be supported by the descriptor instance. * * @return mixed */protectedfunction_descriptorInstance($name,$iface='Descriptor'){// Get an introspection reflection for the class.$class=get_class($this);$class_ref=newReflectionClass($class);// Get the static class property with the given name.$attr=$class_ref->getStaticPropertyValue($name,null);// Check to see if the property is a descriptor instance.if(is_object($attr)){// Get an introspection reflection of the property.$attr_ref=newReflectionClass(get_class($attr));// Check to see if the static property has the right interface.if($attr_ref->implementsInterface($iface)){// Return the found descriptor.return$attr;}}// Return null since we didn't find a matching descriptor.returnnull;}/** * Finds and runs a descriptor method for the class. * * This method will run the specified descriptor method of the descriptor * with the provided name if it exists. The method may be one of 'get', * 'set', or 'unset'. Any arguments provided in the '$args' array parameter * will be passed to the descriptor method. This method will return 'null' * if no matching descriptor instance was found. * * @param string $method The name of the descriptor method to run. * @param string $name The descriptor property name. * @param array $args An array of arguments to pass to the descriptor * method or 'null'. * * @return mixed */protectedfunction_descriptorAccess($method,$name,$args=null){// Initialize descriptor method arguments.if(is_null($args)){$args=array();}// Get the name of the required descriptor interface.$iface='Descriptor'.ucfirst($method);// Retrieve the descriptor instance matching the name and interface.$attr=$this->_descriptorInstance($name,$iface);// Check for a valid descriptor instance.if(!is_null($attr)){// Call the descriptor instance method with the passed arguments.returncall_user_func_array(array($attr,$method.'Descriptor'),$args);}// Trigger an error if the appropriate descriptor wasn't found.trigger_error(sprintf('Undefined property: %s::$%s',$class,$name));returnnull;}/** * Gets a dynamic instance property's value. * * @param string $name The name of the instance property being accessed. * * @return mixed */publicfunction__get($name){$args=array($this,get_class($this));return$this->_descriptorAccess('get',$name,$args);}/** * Sets a dynamic instance property's value. * * @param string $name The name of the instance property being set. * @param string $value The new value to use for the property. * * @return null */publicfunction__set($name,$value){$args=array($this,$value);return$this->_descriptorAccess('set',$name,$args);}/** * Unsets a dynamic instance property. * * @param string $name The name of the instance property being unset. * * @return null */publicfunction__unset($name){$args=array($this);return$this->_descriptorAccess('unset',$name,$args);}/** * Returns true if a descriptor instance exists in the named class property. * * @param string $name The name of the instance property being accessed. * * @return boolean */publicfunction__isset($name){// Simply test to see if we have any descriptor with that name.returnis_null($this->_descriptorInstance($name));}}

The Gotchas

There are some tiny differences between our PHP descriptors and those in
Python, aside from the static access restriction discussed previously. Python’s
property resolution automatically searches for a class property if an instance
does not contain property with the requested name. PHP does not do this, and
treats static properties very differently. This is unlikely to become an issue
in practice, and is a very specific edge case. Another similar detail is that
Python will short circuit its property resolution if a property defines a
"setting" access method, always using the descriptor even if the instance
defines it’s own property with the same name. Descriptors which only handle
"getting" of properties will not be used by default. This is very confusing
and really just an intricacy of Python that doesn’t relate to PHP since we
don’t have the concept of an object’s "data dict." In all except the wildest
of edge cases, these differences may be ignored.

Another small gotcha can pop up when developers erroneously re-include a
source file containing a class definition. Since descriptor instances must
be assigned to a class property and PHP doesn’t allow evaluated variables
as property values in the class definition, we’re forced to add the descriptor
instance to the class after it has been defined. This isn’t something that
should be feared and it’s certainly not "wrong" according to how PHP works,
but it is a little off putting to some developers. Regardless, it’s important
to not re-include a file if it adds a descriptor to a class. Otherwise, a
new instance of the descriptor may suddenly appear in all of the existing
instances of that class. The fix is simple: use require_once like you
should be doing in the first place when importing class definitions.

The Payoff

At this point you’re probably very angry (and crosseyed) after having read
this exhausting tome, and yet we still haven’t explored why descriptors
are useful in the first place. Let’s go over a few benefits descriptors have
over other types of dynamic properties.

Easy (Class-wide) Caching

A classic use case for dynamic properties is value caching. If the value
of a property is expensive to calculate, it’s trivial to set up a dynamic
property that calculates the value once, store it locally in the object
instance and return the calculated value upon subsequent access. The down
side to simple caching (probably using the __get method) is that the
cache is local to the object. That means in naive implementations the
value is calculated and stored each and every time you create an object
instance and try to access the dynamic property. This is unneccessary
when the calculated value will not differ between object instances. Of course
this problem only gets larger the more instances you actually create.

Descriptors open up the possibility of per-class caching, rather than
per-object caching since descriptor instances are defined on a class. Even
when accessed on different object instances, a single descriptor property
instance is handling all of the requests. This allows the descriptor to
be used as a class-wide cache with very little effort. Using a descriptor
in this way can be a lifesaver for intensive operations like database
access:

<?php// Define a descriptor to manage retrieving the high score.classHighScoreDescriptorimplementsDescriptorGet,DescriptorSet{protected$_high_score;// Returns the current high score.publicfunctiongetDescriptor($instance,$owner){// Check to see if we've already retreived the high score.if(is_null($this->_high_score)){// Fetch and cache the high score from the db.$this->_high_score=get_from_db('high_score');}// Return the cached high score.return$this->_high_score;}// Sets the current high score.publicfunctionsetDescriptor($instance,$value){// Update the cached high score.$this->_high_score=$value;// Save the new high score to the db.update_db('high_score',$this->_high_score);}}// Define a scored game class.classScoredGameextendsDescriptable{// Define a class property to hold the high score descriptor.publicstatic$high_score;}// Add a high score descriptor to the class.ScoredGame::$high_score=newHighScoreDescriptor();// Create a game instance.$game=newScoredGame();// Retrieve the high score for the game (db queried).echo'High Score: '.$game->high_score;// Set the high score, cheater.$game->high_score=31337;// Retreive the high score again (db *not* queried).echo'New High Score: '.$game->high_score;// Create another game instance.$another_game=newScoredGame();// Retrieve the high score for the new game (db *still* not queried).echo'Same High Score: '.$another_game->high_score;

Memory Footprint

It’s obvious that caching values with descriptors saves you from executing
expensive operations multiple times, but what about the amount of memory
used? When you cache a variable locally per-instance, you’re storing that
information once for each instance that requests it. Descriptors use far
less memory in this case by only storing one copy. This idea can be extended
into realms other than simple caching, and will always reap the rewards of
leaner memory usage.

Cleaner Than the Alternatives

Implementing a dynamic property with the __get magic method requires
checking the name of the requested property to determine whether or not
it should be dynamically handled. Once that is determined, the __get
method must then figure out what to actually do to calculate the dynamic
value. Innumerable approaches to this problem exist in the wild, and that’s
a problem in itself. There exists no standard way of determining what
should be called, when, and in which context with traditional dynamic
variables. Descriptors provide a standardized interface to these concepts,
and don’t require hacky switch statements with string checking, or a gazillion
setSomething and getSomething stop-gap methods. Descriptions are
cleaner and easier.

True Object Oriented Design

Whether or not you’re an OOP lover (or even a hater) doesn’t matter. You have
to agree that this confusing middle ground in which PHP has been living
sucks. Descriptors promote the concept of PHP classes as objects themselves,
which is what they really are. This is a good move for the language if it
is even going to try to compete with the Pythons and Rubys of tomorrow. If
functionality is class-wide, move it up to the class logic level where it
belongs!

Conclusion

Descriptors are fun, they’re cool, and they’re just an all around good tool
to have in your toolbox. They’re the ideal solution to some very complex,
real world problems that have plagued developers for years. I long for the
day when PHP releases support for dynamic class properties as well so we
can get our hands on a drop-in replacement for Python’s descriptors. Until
then, the descriptor interfaces and descriptable object class we’ve just
designed will fit the bill just fine.