Search code examples
phpooparchitectureobject-oriented-analysissystem-design

Dynamically implement interface based on object property value in PHP?


Let's say that we have a Dog class and a Category class that dogs can be assigned to.

class Dog {
    function categories() {} // Return the categories of the dog.
}

class Category {
    function dogs() {} // Return the dogs under this category.
}

Dogs can have 'pet' and 'shepherd' categories. When assigned to 'pet' category, it's a 'pet dog' and the same goes for 'shepherd'.

Pet dogs and shepherd dogs have different attributes and functions. However, a dog can be both a 'pet dog' and a 'shepherd dog'.

I can imagine having different interfaces for 'pet dog' and 'shepherd dog', e.g.

interface Huggable {
    function hug();
}

interface Trainable {
    function train();
}

Ideally, when a dog is assigned to 'pet' category, it implements the Huggable interface, if it's assigned to 'shepherd' category, it implements the Trainable category.

Is it possible?


Solution

  • As I commented, it is not possible to implement this with PHP natively.

    But you could implement something using decorators, for example.

    A silly decorator approach:

    You'd have your to-be-decorated class:

    class Animal {
    
        protected $categories = [];
    
        public function getCategories() {
            return $this->categories;
        }
    
        public function addCategory( string $category ) {
            // we should check the animal doesn't already belong to this category
            $this->categories[] = $category;
    
        }
    }
    

    Your interfaces, Trainable and Huggable:

    interface Trainable {
    
        function train();
    }
    
    interface Huggable {
        // see https://github.com/php-fig/fig-standards/blob/master/proposed/psr-8-hug/psr-8-hug.md
        function hug() : bool;
    }
    

    One decorator that implements Trainable, and adds the specific category to the decorated instance:

    class PetDecorator extends Animal implements Trainable {
    
    
        public function __construct( Animal $animal ) {
    
            $this->categories = $animal->getCategories();
            $this->addCategory('pet');
    
        }
    
        public function train() {
            echo "I'm housebroken!\n";
        }
    }
    

    And another FluffyDecorator that implements Huggable

    class FluffyDecorator extends Animal implements Huggable {
    
        public function __construct( Animal $animal ) {
    
            $this->categories = $animal->getCategories();
            $this->addCategory('loveBear');
        }
    
        public function hug( ) :bool {
            echo "Much hug!\n";
            return true;
        }
    }
    

    Finally, you'd use it thus:

    $fido    = new Animal();
    $fidoPet = new PetDecorator($fido);
    
    $fidoPet->train();
    // I'm housebroken!
    
    print_r($fidoPet->getCategories());
    /*
    Array
    (
        [0] => pet
    )
    */
    
    $fidoLove = new FluffyDecorator($fidoPet);
    // Much hug!
    $fidoLove->hug();
    
    print_r($fidoLove->getCategories());
    /*
     Array
    (
        [0] => pet
        [1] => loveBear
    )
     */
    

    The many-to-many relationship between "Dogs" and "Categories" I leave up to you. That's a separate issue and could be handled in many different ways.