5
\$\begingroup\$

I've written a simple class that somewhat simulates multiple inheritance using mixins. It allows you to extend the functionality of multiple classes and manage any conflicts between them, should they arise.

Mixer class

abstract class Vm_Mixer {

    protected $methods = array();
    protected $mixins = array();
    protected $priorities = array();

    /**
     * @description By adding a mixin, the class will automatically adopt all of a mixin's methods.
     * @param object $mixin - The instantiated object that's methods should be adopted by the extending class.
     */
    public function addMixin($mixin){
        $name = get_class($mixin);
        $this->mixins[$name] = $mixin;
        $methods = get_class_methods($name);
        $this->methods[$name] = $methods;
    }

    /**
     * @description Gets the class's current mixins by name
     * @return An array of mixin names
     */
    public function getMixins(){
        return array_keys($this->methods);
    }

    /**
     * @description Manages conflicts for the mixins.
     * @param array $priorities - The method name as the key, the class name that has priority in a conflict as the value.
     */
    public function setPriorities(array $priorities){
        $this->priorities = $priorities;
    }

    /**
     * @description Magic method that calls the mixin methods automatically
     * @param string $methodName - The name of the mixin method
     * @param array $arguments - The arguments for the method
     */
    public function __call($methodName, $arguments){
        foreach ($this->methods as $className=>$methods){
            if (in_array($methodName, $methods)){
                if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
                    call_user_func_array(array($className, $methodName), $arguments);
                    break;
                } else if (!in_array($methodName, array_keys($this->priorities))){
                    call_user_func_array(array($className, $methodName), $arguments);
                    break;                  
                }
            } 
        }
    }
}

Example

Here is a quick example of how this could be used:

Mixin 1

class Weapons {

    public function laser(){
        echo 'Firing laser!<br/>';
    }

    public function catapult(){
        echo 'Launching catapult! MEEEOOOWWW!<br/>';
    }

    public function explode(){
        echo 'Boom!<br/>';
    }

    public function greet(){
        echo 'Prepare for your doom!<br/>';
    }
}

Mixin 2

class Defense {

    public function shield(){
        echo 'Enabling shield!<br/>';
    }

    public function invisibility(){
        echo 'Activating invisibility cloak!<br/>';
    }

    public function explode(){
        echo 'Self destruct sequence activated!<br/>';
    }

    public function serve(array $foodItems){
        echo '#Sets table with '.implode(', ', $foodItems).'#<br/>';
    }
}

Robot class

class Robot extends Vm_Mixer {

    protected $name;

    public function __construct($name){
        $this->name = $name;
        $this->addMixin(new Weapons());
        $this->addMixin(new Defense());

        //The explode method has a conflict as it exists in both Weapons and Defense, so let's give precedence to Defense
        $this->setPriorities(array('explode'=>'Defense'));
    }

    //Pre-empts the Weapons::greet method
    public function greet(){
        echo "Hi, my name is $this->name.</br>";
    }
}

Usage

$robot = new Robot('Robby');
$robot->greet();
$robot->shield();
$robot->laser();
$robot->explode();
$robot->serve(array('Pizza', 'Cheeseburgers', 'Egg Nog', 'Vanilla Pudding'));

The above will return:

Hi, my name is Robby.  
Enabling shield!  
Firing laser!  
Self destruct sequence activated! 
#Sets table with Pizza, Cheeseburgers, Egg Nog, Vanilla Pudding#

What I'm looking for in a review is as follows:

  • How much will performance be an issue with this implementation (I'm mainly talking about method overloading here)?
  • Are there any other issues that I might run into that could cause conflicts or problems?
  • How can I improve the existing code?
  • What are some ways that this could be used? I know a few, but other ideas would be nice as well.
  • When should and shouldn't something like this be used?
\$\endgroup\$
  • \$\begingroup\$ > How can I improve the existing code? Your missing an implementation for Vm_Mixer_Exception(). Why were you using a custom Exception class? Was there more information than a regular Exception would give you? \$\endgroup\$ – user341493 Apr 12 '13 at 19:11
4
\$\begingroup\$

It's an interesting approach and it works as expected. My first reaction when reading the code was that you are trying to solve a problem that's commonly solved with the Composite pattern, but then I realised you aren't actually trying to solve a problem but present an idea.

Now as for the specifics:

How much will performance be an issue with this implementation (I'm mainly talking about method overloading here)?

Unless you find a realistic scenario, you can't discuss performance. Commonly method overloading and the Reflection API are considered to be very slow, but performance is not about being slow, it's about comparing solutions. Unless we find a realistic use of your architecture and at least one alternative, performance doesn't matter.

Are there any other issues that I might run into that could cause conflicts or problems?

You are dealing with the Diamond Problem with the priorities scheme but you don't do anything for cases of equal priority. These might be outside of your scope, but the following snippet shows that relying on a priority list is extremely vulnerable:

$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));
$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));

At any point the priority list can be changed, and the last one will be the actual one. This kind of dynamic behaviour can lead to myriads of extremely hard to find bugs, you should consider throwing an exception if a priority was already set.

Furthermore you don't check for priority validity and you could do something like:

$robot->setPriorities(array('explode'=>'aaaa'));

since you don't check if the priority class actually exists and set the priority anyway, which will render explode() uncallable. And of course you could do something evil like:

$robot->setPriorities(array('explode'=>'Robot'));

which would throw your robot into an infinite loop. So you haven't actually dealt with every aspect of the Diamond Problem. You should also check if the class exists in addMixin().

How can I improve the existing code?

Here:

public function __call($methodName, $arguments){
    foreach ($this->methods as $className=>$methods){
        if (in_array($methodName, $methods)){
            if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
                call_user_func_array(array($className, $methodName), $arguments);
                break;
            } else if (!in_array($methodName, array_keys($this->priorities))){
                call_user_func_array(array($className, $methodName), $arguments);
                break;                  
            }
        } 
    }
}
  • it would be a lot nicer if you returned the value of the "Mixin" class instead of just calling it and breaking,
  • you should deal with the case of a non existing function,
  • the block is kind of unreadable.

I'd refactor like:

public function __call($methodName, $arguments){
    foreach ($this->methods as $className => $methods) {        
        if(!in_array($methodName, $methods)) {
            continue;
        }

        if(
            in_array($methodName, array_keys($this->priorities))
            && $className == $this->priorities[$methodName]
        ) {
            return call_user_func_array(array($className, $methodName), $arguments);
        } else if (!in_array($methodName, array_keys($this->priorities))) {
            return call_user_func_array(array($className, $methodName), $arguments);                 
        } 
    }

    throw new Exception("Function not found"); // or something like that
}

And probably you should somehow deal with Exceptions thrown from the called functions.

What are some ways that this could be used? I know a few, but other ideas would be nice as well.

When should and shouldn't something like this be used?

I have absolutely no clue. I've trained myself to forget all about multiple inheritance, since my programming roots were in C++. It would be nice if you tell us about your ideas that made you come up with the approach.

PHP 5.4 will support traits, a concept very similar to mixins. So, unfortunately your approach will soon become obsolete.

\$\endgroup\$
  • \$\begingroup\$ Thanks, Yannis, very useful. I had done some reading on component based programming and so I wanted to experiment with it a little. Take a look at Evolve Your Hierarchy and Component based game engine design for an introduction. The primary use case that I had in mind is game programming, similar to the first article I linked to. Can you expand a little on cases of equal priority? I can't think of an example of where that would come up. \$\endgroup\$ – VirtuosiMedia Nov 26 '11 at 20:31
  • \$\begingroup\$ @VirtuosiMedia I've updated the answer. Just finished reading "Evolve Your Hierarchy" and I think it strongly hints towards the Composite pattern. Here's a PHP oriented introduction. Another pattern you could use in conjunction with composite and it might prove useful performance-wise is flyweight. BTW what you're doing is somewhere between mixins and composite, as they are very closely related. \$\endgroup\$ – yannis Nov 26 '11 at 21:08
  • \$\begingroup\$ Thanks again, Yannis. You're right on needing to have exceptions. I've updated the class to reflect your suggestions. Regarding the __call method, I think that I need both of those checks because a class could contain the called method but not be the one given the priority. The second check prevents those types of methods from being executed. Otherwise, the first class with the called method would automatically be executed. \$\endgroup\$ – VirtuosiMedia Nov 27 '11 at 2:35
  • \$\begingroup\$ Regarding the composite pattern, I might be misunderstanding it, but wouldn't it require each of the mixins to share an interface? If so, that seems a little different than my implementation, where no restrictions are put on the mixins. \$\endgroup\$ – VirtuosiMedia Nov 27 '11 at 2:39
  • \$\begingroup\$ @VirtuosiMedia Yes of course it would be a different implementation, and I'm not suggesting it be a better implementation. But why would it be bad if your mixins shared an interface? I've noticed why you need both checks when I last updated the question to expand the "equal priority" section and rollbacked the code to include both checks as it did originally. \$\endgroup\$ – yannis Nov 27 '11 at 3:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.