是否可以扩展对象的功能或将方法导入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.

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
";
    }

}

class FooUser extends UserDecorator
{
    public function foo()
    {
        // do some foo
        return "doing fooness
";
    }
}

// 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

What you describe is not possible, have you considered a different approach? What if your User class 'contained' various privilege implementations in an array.

Consider it as an 'Is A' and 'Has A' problem; while an AdminUser 'is' an admin, it 'has' privileges, so said priv's should be stored as member variables.

I think your scenario might call for a Factory Pattern

Perhaps you shouldn't have instantiated a OrdinaryUser in the first place~

This is not immediately possible in PHP right now. Here are some alternatives, in order of ease of implementation.

First, if you know all of the possible transformations between related classes, you can easily create a method that takes the current object, populates a clean instance of the new class and returns it. This is the idea you mentioned in your original question. It's the most straightforward and safe thing you could do.

Second, if you're using a modern enough version of PHP, you can use the Serializable interface for a neat trick. If you implement that interface, __sleep/__wakeup are never called, and neither is the constructor. This means that you can use those methods for a cheap trick. Here's some silly demo code without the interface to demonstrate:

[mcg@mcg-workstation ~]$ php -a
Interactive shell

php > class Foo { public $a; public $b; }
php > class Bar extends Foo { public $c; }
php > class Baz extends Foo { public $c; }
php > $one = new Bar();
php > $one_s = serialize($one);
php > echo $one_s;
O:3:"Bar":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $one_s = explode(':', $one_s, 4);
php > print_r($one_s);
Array
(
    [0] => O
    [1] => 3
    [2] => "Bar"
    [3] => 3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
)
php > $two_s = $one_s;
php > $two_s[1] = strlen('Baz'); $two_s[2] = '"Baz"';
php > $two_s = join(':', $two_s);
php > echo $two_s;
O:3:"Baz":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $two = unserialize($two_s);
php > echo get_class($two);
Baz

If you didn't follow, this code replaces the class name in the serialized data. By doing this, I've just transformed a Bar into a Baz, with all of the same properties. This only really works if the properties are identical. I'm not sure what PHP would do if the properties don't match, and if you implement Serializable, your serialize and unserialize methods will need to handle the transformation.

This is also a tremendous hack that may cause future maintainers of your code to want to track you down and hurt you. There's also probably a minor performance penalty. If you end up using it, be sure to benchmark. And hire bodyguards. Or at least make sure the future maintainers won't be murderous psychopaths that can find your address.

Third, going back to class-based construction instead of object-based construction: If you can wait for PHP 5.4 (or whatever the current trunk will end up being), you will be able to place anonymous functions in properties and call them as if they were methods. While you can place anonymous functions in properties in earlier versions, at least as of PHP 5.3, those functions can not reference $this, and are therefore quite useless as part of an object. This limitation is also what prevents you from using the __call magic method to achieve the same thing right now.

Fourth, and this is a total non-answer, consider Ruby or Perl if you want these kind of class-bending gymnastics. I think Python can do similar things, but I haven't worked in it and can't be sure. PHP is not a flexible language when it comes to OO, and the people on the internals list have no interest in bringing OO up to the more interesting standards of other languages.


With regard to your related problem, it sounds like you really want instance-level traits. PHP 5.4 will also have traits, but at the class level, not the instance level. See #4 for my commentary about that.