Search code examples
phpinheritanceobjectmultiple-inheritanceobjectinstantiation

Is it possible to extend the functionality of an object or to Import methods into an object in PHP?


I am facing a serious design problem that is driving me crazy. I think it can only be solved with multiple inheritance or something. So here is what I want to do:

Say I have a basic user class called OrdinaryUser defined in this way:

class OrdinaryUser
{
 private $id,$name;

 public function __construct($id)
 {
  $this->id = $id;
  $this->name = fictionalDB::getUserNameById($id);
 }

 public function getName()
 {
  return $this->name;
 }

 public function getId()
 {
  return $this->id;
 }

}

And I have a subclass called AdminUser with additional functionality:

class AdminUser extends OrdinaryUser
{
 public function deleteUser($id)
 {
  echo "Deleting user where id=$id";
 }
}

The problem: What if I have already instantiated an object of type "OrdinaryUser" and want to make it into an AdminUser object on-the-fly? Is there a way of "extending objects" so to avoid instantiating a subclass and having to re-populate the new object's fields with the same data?

Another related problem: I might have many other categories of users defined later, each having their own unique bahaviour, but always a basic one, and it wouldn't make sense to create a hierarchy in this case because most times one type of object should not be inheriting methods from the other type, although it might be desirable to have additional functionality from one type being "imported" into the other dinamically.


Solution

  • Sounds like the Decorator Pattern may help you

    <?php
    /*
       an interface to ensure we only decorate Users
       and possibly some of the most common methods that all users have
       so that we don't always suffer the overhead of the magic __call method
    */
    interface User
    {
        public function getId();
        public function getName();
    }
    
    class OrdinaryUser implements User
    {
        private $id,$name;
    
        public function __construct($id)
        {
            $this->id = $id;
            $this->name = fictionalDB::getUserNameById($id);
        }
    
        public function getName()
        {
            return $this->name;
        }
    
        public function getId()
        {
            return $this->id;
        }
    
    }
    
    /*
       There aren't any abstract methods in this class
       but it is declared abstract because there is no point in instantiating one
    */
    abstract class UserDecorator implements User
    {
        protected $user;
    
        public function __construct( User $user )
        {
            $this->user = $user;
        }
    
        public function getId()
        {
            return $this->user->getId();
        }
    
        public function getName()
        {
            return $this->user->getName();
        }
    
        /*
           Any methods that aren't implemented by this type of
           user are dealt with by the decorated user
        */
        public function __call( $method, $arguments )
        {
            return call_user_func_array( array( $this->user, $method ), $arguments );
        }
    }
    
    class AdminUser extends UserDecorator
    {
        /*
           Add any methods that are particular to this type of user
        */
        public function addedMethod()
        {
            // do AdminUser type stuff
            return "doing added method stuff\n";
        }
    
    }
    
    class FooUser extends UserDecorator
    {
        public function foo()
        {
            // do some foo
            return "doing fooness\n";
        }
    }
    
    // just for testing
    class fictionalDB
    {
        public static function getUserNameById( $id )
        {
            $db = array(
                1 => 'Peter',
                2 => 'Paul'
            );
            return $db[$id];
        }
    }
    
    
    $user = new OrdinaryUser( 1 );
    echo $user->getName();    // Peter
    
    // make Peter into an AdminUser
    $user = new AdminUser( $user );
    
    // and also add some fooness
    $user = new FooUser( $user );
    echo $user->addedMethod(); // doing added method stuff
    echo $user->foo();         // doing fooness