Method Injection

assemble an interface from many parts (objects)

Note: "method injection" normally refers to dependency injection via a method other than the constructor. I find that badly named. If "dependency injection" injects some sort of service object into a client object, "method injection" suggests adding extra methods to a client object, ie a mixin/traits type of system. It may be too late to try to change things though...

How to decorate a class with some new features?

In the simplest case you could subclass but inheritance can be limiting. What if you have a dozen different classes implementing some kind of interface? You wouldn't really want to extend each one with the same copy-paste code. (For the purposes of this discussion, creating an abstract superclass and adding the shared functionality to that isn't an option. It might not make sense for one reason or another — not least when the classes are part of someone else's library and have to be used as-is).

The problem just gets worse if multiple decorators might be needed, or if these need to be used together in varying combinations at different times. Theoretically, you could stick them all in a bloated inheritance hierarchy, one on top of another, and then repeat for each of the dozen decoratable classes. Well you could...

If inheritance is a bad choice — as it often is — you can aggregate instead. A decoratable instance can be wrapped in a decorator which passes existing method calls on untouched and adds a few new methods of its own. One nice thing about this is that decoratable knows nothing about the decoration. It doesn't need to be edited in any way. However, this could cause some problems in a type-hint-aware context. Clients are presented with the decorator type, not that of decoratable. Although in general I don't consider type hints to be very useful I am a big fan of dependency injection (which often uses type hint prompts).

Another option is to "inject" methods into decoratable, as described below. With a bit of __call() magic, the public methods of an aggregated decorator can be called as if they were methods of the decoratable instance. In contrast to a decorator-wrapper, everything happens internally and hence the object type presented to clients is not changed. However the decoratable class will need to be edited to support method injection, and decorators can't override decoratable.

This works well if there's a need to arbitrarily combine discrete toolsets in a decoratable object. As an example, see AperiTestCase. When testing, a variety of fixture tools and expectations are required for different domains and sometimes these must be mixed up together in various combinations in different test cases. Method injection provides an open plug-in system where an object interface can be assembled from many parts.

A decorator wrapper can achieve much the same ends, with some minor differences as mentioned above. Also note that wrappers which implement the same method will override each other silently and that might lead to some unexpected behaviour if you're not paying attention. In contrast, method injection (as implemented below) provides some basic decorator management. Two decorators which try to inject the same method will cause an exception to be thrown. Also, a couple of debugging methods are provided to help explain where messages are being picked up. This might be more useful when things start to get complicated.

Note that method injection isn't something you'd want to use too often. There's a danger of poor cohesion if behaviours which ought to form a single unit (ie class) are spread too widely. Always look for the single unit first. The method injection motto should perhaps be: "mixed in but not mixed up".

Trait Me Right Baby

There's enough method-injection logic to justify a dedicated class: DecoratorStack. To make a class decoratable, just hook an instance up to __call() by adding the two methods shown below:

<?php    

class MyDecoratorableClass {

    function 
__call($method$args) {
        return 
call_user_func_array(
            array(
$this->decorators(), $method), 
            
$args);
    }
    function 
decorators() {
        if( !isset(
$this->decorators)) {
            require_once(
'aperiplus/lib/misc/method-injection.php');
            
$this->decorators = new DecoratorStack($this);
        }
        return 
$this->decorators;
    }
    ...
    ...
}

DecoratorStack injects its own methods into decoratable:

To add a decorator:

<?php    

class FooDecorator {

    function 
foo() {
        ...
    }
}

$decoratable = new MyDecoratorableClass;
$decoratable->mixin(new FooDecorator);
$decoratable->foo(); // resolves to FooDecorator::foo()

Multiple decorators may be added and they can be mixed and matched in (almost) any combination. The only restriction is that no two decorators may attempt to inject the same method. A DecoratorMethodConflict exception will be thrown. Non-public methods are of course ignored by the check (and will not be injected).

In my own code I use undescores to label non-public methods. The single-implementor rule may not respect code which uses private/protected (if it does it's accidental: it certainly hasn't been tested). I could add support for that if there was a demand but I'd much prefer if the rest of the world would stop worrying unnecessarily about access restriction.

Of course a foo() method in decoratable overrides any decorator foo(). The call is intercepted before it ever gets to the decorator stack. If you need to check which object receives a given type of message use one of the debugging methods: whoImplements($method) or getInterface().

Dependency Injection

In mixin(), instead of instantiating and passing an object, you can simply specify a decorator name:

<?php    

$decoratable
->mixin('FooDecorator');

Internally, Phemto will assemble a FooDecorator instance. Note that Phemto will automatically fill any type-hinted object dependencies. Anything else can be passed in the mixin call:

<?php    

$decoratable
->mixin('AnotherDecorator'$arg1$arg2, ...etc));

This will only work in simple cases. The default Phemto instance used by by mixin hasn't been configured and hence is limited to Phemto's basic capabilities. For example, if you ask for a Foo interface, and only one class (in all included files) implements Foo, everything's fine. If two classes implement Foo, the unconfigured Phemto can't tell which one you want. Hence, you can also pass in an (externally configured) injector instance. Probably the best place to do this is the decorators() factory method:

<?php    

class MyDecoratorableClass {

    ...
    ...
    function 
decorators() {
        if( !isset(
$this->decorators)) {
            require_once(
'aperiplus/lib/misc/method-injection.php');
            
$this->decorators = new DecoratorStack($this);
            
$this->decorators->setInjectorUsedByMixin($this->_injector);
        }
        return 
$this->decorators;
    }
    ...
    ...
}

MyDecoratorableClass, or something upstream, can configure the injector as required.

Type-hint based dependency injection was originally added simply to save a bit of work assembling decorator objects but of course this also provides a means of configuring behaviours. Mixin generic interfaces or abstract classes rather than concrete implementations. An injector (passed in via setInjectorUsedByMixin) can fill these dependencies and the precise decorator implementations will be controlled by an easy-to-find injector configuration file.

Ignorable Methods

It can happen that decorator objects may have a dual role, with some additional function outside of their decorator context. This means we have to tweak the method injection logic by adding the idea of "ignorable methods". Ignorable methods aren't meant to decorate the decoratable object and so they're kept out of the method injection loop. Multiple decorators should be allowed to implement an ignorable method and calls to ignorable methods should not be passed on to the decorator stack.

"Dual role" should instantly activate a code smell alarm but, as an example, in AperiTestCase, fixture objects are (usually) also test case decorators and so multiple implementations of skip, setUp etc must be allowed in the decorator stack. These methods are never actually called via the method injection framework but instead are plugged in directly to the setUp/tearDown cycle.

If you need this feature, optionally pass ignorable methods (as an array) to DecoratorStack at instantiation:

<?php    

class MyDecoratorableClass {

    function 
__call($method$args) {...}

    function 
decorators() {
        if( !isset(
$this->decorators)) {
            require_once(
'aperiplus/lib/misc/method-injection.php');
            
$this->decorators = new DecoratorStack(
                
$this
                array(
'ignorableMethod_1''ignorableMethod_2'));
        }
        return 
$this->decorators;
    }
    ...
    ...
}

SourceForge.net Logo