代码之家  ›  专栏  ›  技术社区  ›  Nathan MacInnes

将当前对象($this)转换为子类

  •  32
  • Nathan MacInnes  · 技术社区  · 14 年前

    我有一个类,在该类中,可能需要将对象更改为行下更远的子类。这可能吗?我知道有一个选项是返回它的副本,但使用子类,但最好实际修改当前对象。。。所以:

    class myClass {
      protected $var;
    
      function myMethod()
      {
        // function which changes the class of this object
        recast(myChildClass); 
      }
    }
    
    class myChildClass extends myClass {
    }
    
    $obj = new myClass();
    $obj->myMethod();
    get_class_name($obj); // => myChildClass
    
    5 回复  |  直到 14 年前
        1
  •  9
  •   ssice    7 年前

    如其他答案所述,你可以用下流的 PECL扩展。

    不过,你真的不想要。任何你想在OOP中解决的问题都有一个符合OOP的方法。

    运行时类型层次结构修改不符合OOP(事实上,这是有意识地避免的)。有些设计模式应该符合您的要求。

        2
  •  35
  •   ircmaxell    11 年前

    在PHP中,转换以更改对象的类型是不可能的(没有使用讨厌的扩展)。一旦你实例化了一个对象,你就不能再改变类(或其他实现细节)了。。。

    您可以使用如下方法模拟它:

    public function castAs($newClass) {
        $obj = new $newClass;
        foreach (get_object_vars($this) as $key => $name) {
            $obj->$key = $name;
        }
        return $obj;
    }
    

    $obj = new MyClass();
    $obj->foo = 'bar';
    $newObj = $obj->castAs('myChildClass');
    echo $newObj->foo; // bar
    

    但要注意的是,它实际上并没有改变原来的类。它只是创造了一个新的。注意,这需要属性是公共的,或者有getter和setter魔术方法。。。

    castAs 为了防止出现问题:

    if (!$newClass instanceof self) {
        throw new InvalidArgumentException(
            'Can\'t change class hierarchy, you must cast to a child class'
        );
    }
    

    好吧,既然Gordon发布了一个非常黑的魔法解决方案,我也会这么做(使用 RunKit PECL扩展(警告: ):

    class myClass {}
    class myChildClass extends MyClass {}
    
    function getInstance($classname) {
        //create random classname
        $tmpclass = 'inheritableClass'.rand(0,9);
        while (class_exists($tmpclass))
            $tmpclass .= rand(0,9);
        $code = 'class '.$tmpclass.' extends '.$classname.' {}';
        eval($code);
        return new $tmpclass();
    }
    
    function castAs($obj, $class) {
        $classname = get_class($obj);
        if (stripos($classname, 'inheritableClass') !== 0)
            throw new InvalidArgumentException(
                'Class is not castable'
            );
        runkit_class_emancipate($classname);
        runkit_class_adopt($classname, $class);
    }
    

    所以,与其这么做 new Foo

    $obj = getInstance('MyClass');
    echo $obj instanceof MyChildClass; //false
    castAs($obj, 'myChildClass');
    echo $obj instanceof MyChildClass; //true
    

    从类内(只要它是用 getInstance ):

    echo $this instanceof MyChildClass; //false
    castAs($this, 'myChildClass');
    echo $this instanceof MyChildClass; //true
    

    免责声明:不要这样做。真的,不要。这是可能的,但这是个可怕的主意。。。

        3
  •  12
  •   Gordon Haim Evgi    14 年前

    重新定义类

    你可以用 runkit PECL extension 又称“地狱工具箱”:


    重新定义实例

    runkit函数在对象实例上不起作用。如果您想在对象实例上这样做,理论上可以通过处理序列化的对象字符串来实现。
    这是一个 黑魔法 不过。

    下面的代码允许您将实例更改为任何其他类:

    function castToObject($instance, $className)
    {
        if (!is_object($instance)) {
            throw new InvalidArgumentException(
                'Argument 1 must be an Object'
            );
        }
        if (!class_exists($className)) {
            throw new InvalidArgumentException(
                'Argument 2 must be an existing Class'
            );
        }
        return unserialize(
            sprintf(
                'O:%d:"%s"%s',
                strlen($className),
                $className,
                strstr(strstr(serialize($instance), '"'), ':')
            )
        );
    }
    

    class Foo
    {
        private $prop1;
        public function __construct($arg)
        {
            $this->prop1 = $arg;
        }
        public function getProp1()
        {
            return $this->prop1;
        }
    }
    class Bar extends Foo
    {
        protected $prop2;
        public function getProp2()
        {
            return $this->prop2;
        }
    }
    $foo = new Foo('test');
    $bar = castToObject($foo, 'Bar');
    var_dump($bar);
    

    结果:

    object(Bar)#3 (2) {
      ["prop2":protected]=>
      NULL
      ["prop1":"Foo":private]=>
      string(4) "test"
    }
    

    Bar 对象,所有属性都保持其可见性,但是 prop2 NULL 酒吧 儿童 Foo ,它处于无效状态。你可以加个魔法 __wakeup 以某种方式处理这个问题的方法,但是说真的,你不想这样,这就说明了为什么选演员是一件难看的事情。

    免责声明:我绝对不鼓励任何人在生产中使用这些解决方案。

        4
  •  2
  •   Alan Geleynse buhbang    14 年前

    这是不可能的,因为虽然子类的实例也是父类的实例,但反之亦然。

    您可以创建子类的新实例,并将旧对象中的值复制到该实例上。然后,您可以返回类型为 myChildClass .

        5
  •  1
  •   Thomas Kekeisen    6 年前

    对于简单的类,这可能有效(我在一些罕见的情况下成功地使用了此功能):

    function castAs($sourceObject, $newClass)
    {
        $castedObject                    = new $newClass();
        $reflectedSourceObject           = new \ReflectionClass($sourceObject);
        $reflectedSourceObjectProperties = $reflectedSourceObject->getProperties();
    
        foreach ($reflectedSourceObjectProperties as $reflectedSourceObjectProperty) {
            $propertyName = $reflectedSourceObjectProperty->getName();
    
            $reflectedSourceObjectProperty->setAccessible(true);
    
            $castedObject->$propertyName = $reflectedSourceObjectProperty->getValue($sourceObject);
        }
    }
    

    在我的案例中的用法:

    $indentFormMapper = castAs($formMapper, IndentedFormMapper::class);
    

    $castedObject = castAs($sourceObject, TargetClass::class);
    

    当然 TargetClass 必须从 sourceObject 你必须公开所有受保护的私人财产 目标类 为了得到这份工作。

    FormMapper ( https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Form/FormMapper.php )飞到 IndentedFormMapper chain :

    class IndentedFormMapper extends FormMapper
    {
        /**
         * @var AdminInterface
         */
        public $admin;
    
        /**
         * @var BuilderInterface
         */
        public $builder;
    
        /**
         * @var FormBuilderInterface
         */
        public $formBuilder;
    
        /**
         * @var string|null
         */
        public $currentGroup;
    
        /**
         * @var string|null
         */
        public $currentTab;
    
        /**
         * @var bool|null
         */
        public $apply;
    
        public function __construct()
        {
        }
    
        /**
         * @param $callback
         * @return $this
         */
        public function chain($callback)
        {
            $callback($this);
    
            return $this;
        }
    }