This is not technically a feature request at this point, I’m mostly posting this idea for discussion/review.
I wrote a simple class that implements the decorator pattern in PHP - and here it is, with a simple example:
<?php
class CDecorator
{
private static $registry=array();
protected static function register($className, CDecorator $decorator)
{
$decoratorClass = get_class($decorator);
$class = new ReflectionClass($decoratorClass);
foreach ($class->getMethods() as $method)
{
if ($method->isProtected())
{
@self::$registry[$className][$method->name] = array($decorator, $method->name);
}
}
}
public static function invoke($object, $name, $arguments=null)
{
$callback = self::$registry[get_class($object)][$name];
return call_user_func_array($callback, array_merge(array($object), $arguments));
}
public function __construct($className)
{
self::register($className, $this);
}
}
class User
{
public $firstName;
public $lastName;
public function __call($name, $arguments)
{
return CDecorator::invoke($this, $name, $arguments);
}
}
new NameDecorator('User');
class NameDecorator extends CDecorator
{
protected function getFullName($object)
{
return $object->firstName . ' ' . $object->lastName;
}
}
$user = new User;
$user->firstName = 'Rasmus';
$user->lastName = 'Schultz';
echo $user->getFullName();
In this example, the User class is being decorated by the NameDecorator class with a getFullName() method.
The CDecorator class serves both as a simple manager/registry for decorations, as well as a base-class for decorators.
To apply a decorator to a class, you simply place a statement like new DecoratorClass(‘DecoratedClassName’) above or below your decorated class - in the file, outside the class declaration, that is.
This forces decorators to load immediately - lazy-loading decorators is not possible, but lazy-loading a decorated class is of course still possible, although the decorator will always load as soon as the decorated class loads.
This specific example is very simple - the idea is that a number of classes that all need to know how to concatenate first and last names into a full name. Using a decorator, these classes can share the getFullName() method implementation, without belonging to the same object hierarchy.
How is this different from CBehavior?
CBehavior maintains a list of decorators per instance - this is initialized every time an object is constructed. CDecorator instead maintains a single, static list of decorator methods, per class.
CBehavior lets you decorate a single object, dynamically, at run-time, on demand - while CDecorator lets you decorate a class, statically, at load-time.
So in terms of functionality, not much difference - both approaches enable you to add methods to an object.
The main difference is in terms of performance. While you could implement a NameBehavior using CBehavior, and get a result that is functionally equivalent to this example, the behaviors would be constructed and associated with each new object.
Of course, you could probably construct your NameBehavior once, using a singleton pattern, and assign the same NameBehavior instance each time an object is constructed - the only overhead then, as compared to a decorator, is the maintenance of the behavior-list in each instance, plus the overhead of searching them for the requested method.
Maybe there is not much to gain in terms of performance, except perhaps in cases where you have many behaviors actually just working as decorators - but those cases may be rare.
So it mostly comes down to the question, is the decorator pattern necessary or useful in Yii?
Or do behaviors already do the job?