php 将当前对象 ($this) 转换为后代类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4080217/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 11:53:35  来源:igfitidea点击:

Cast the current object ($this) to a descendent class

phpooptype-conversiondowncast

提问by Nathan MacInnes

I have a class where it may be necessary to change the object to a descendent class further down the line. Is this possible? I know that one option is to return a copy of it but using the child class instead, but it'd be nice to actually modify the current object... so:

我有一个类,可能需要将对象更改为后续的后代类。这可能吗?我知道一种选择是返回它的副本,但使用子类代替,但实际修改当前对象会很好......所以:

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

采纳答案by ssice

You can, as described in other answers, do it with nasty black magicPECL extensions.

正如其他答案中所述,您可以使用讨厌的黑魔法PECL 扩展来实现。

Though, you seriously don't want it. Any problem you want to solve in OOP there's an OOP-compliant way to do it.

虽然,你真的不想要它。您想在 OOP 中解决的任何问题都有符合 OOP 的方法来解决。

Runtime type hierarchy modifications are not OOP-compliant (in fact, this is consciously avoided). There are design patterns that should fit what you want.

运行时类型层次结构修改不符合 OOP(实际上,这是有意避免的)。有一些设计模式应该适合你想要的。

Please, tell us why do you want that, I'm sure there must be better ways to do it ;)

请告诉我们你为什么要那样做,我相信一定有更好的方法来做到这一点;)

回答by ircmaxell

Casting to change the object's type is not possible in PHP (without using a nasty extension). Once you instantiate a object, you can't change the class (or other implementation details) anymore...

在 PHP 中无法通过转换来更改对象的类型(不使用讨厌的扩展)。一旦实例化一个对象,就不能再更改类(或其他实现细节)...

You can simulate it with a method like so:

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

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

Usage:

用法:

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

But beware that it doesn't actually change the original class. It just creates a new one. And beware that this requires that the properties are public or have getter and setter magic methods...

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

And if you wanted some more checks (I'd suggest so), I'd add this line as the first line of castAsto prevent issues:

如果您想要更多检查(我建议这样做),我会将此行添加为第一行castAs以防止出现问题:

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


Alright, since Gordon posted a very black-magic solution, I will do the same (using the RunKitPECL extension (warning: here be dragons):

好吧,既然 Gordon 发布了一个非常黑魔法的解决方案,我会做同样的事情(使用RunKitPECL 扩展(警告:这里是Dragons):

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

So, instead of doing new Foo, you'd do something like this:

所以,不是做new Foo,你会做这样的事情:

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

And from within the class (as long as it was created with getInstance):

并从类中(只要它是用创建的getInstance):

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

Disclaimer: Don't do this. Really, don't. It's possible, but it's such a horrible idea...

免责声明:不要这样做。真的,不要。这是可能的,但这是一个如此可怕的想法......

回答by Gordon

Redefining Classes

重新定义类

You can do this with the runkit PECL extensionaka the "Toolkit from Hell":

您可以使用runkit PECL 扩展(又名“来自地狱的工具包”)来执行此操作:

  • runkit_class_adopt— Convert a base class to an inherited class, add ancestral methods when appropriate
  • runkit_class_emancipate— Convert an inherited class to a base class, removes any method whose scope is ancestral


Redefining Instances

重新定义实例

The runkit functions do not work on object instances. If you want to do that on object instances, you could theoretically do that by messing with the serialized object strings.
This is the realms of black magicthough.

runkit 函数不适用于对象实例。如果你想在对象实例上这​​样做,理论上你可以通过处理序列化的对象字符串来做到这一点。
不过,这是黑魔法的领域。

The code below allows you to change an instance to whatever other class:

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

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), '"'), ':')
        )
    );
}


Example:

例子:

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);

Result:

结果:

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

As you can see, the resulting object is a Barobject now with all properties retaining their visibility but prop2is NULL. The ctor doesnt allow this, so technically, while you have a Barchild of Foo, it is not in a valid state. You could add a magic __wakeupmethod to handle this somehow, but seriously, you dont want that and it shows why casting is ugly business.

正如你所看到的,所产生的对象是一个Bar对象现在所有属性保持自己的知名度,但是prop2NULL。ctor 不允许这样做,因此从技术上讲,虽然您有 的Bar孩子Foo,但它处于无效状态。你可以添加一个神奇的__wakeup方法来以某种方式处理这个问题,但说真的,你不想要那个,这说明了为什么铸造是一件丑陋的事情。

DISCLAIMER: I absolutely do not encourage anyone to use any of these solutions in production.

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

回答by Alan Geleynse

This is not possible because while an instance of a child class is also an instance of a parent class, the reverse is not true.

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

What you could do is create a new instance of the child class and copy the values from the old object onto it. You can then return the new object which will be of type myChildClass.

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

回答by Thomas Kekeisen

For simple classes this may work (I am using this successfully in some rare cases):

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

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

Usage in my case:

在我的情况下的用法:

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

More abstract:

更抽象:

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

Of course TargetClasshas to inherit from the class of sourceObjectand you have to make all protected and private properties public in TargetClassto get this work.

当然TargetClass必须从类继承sourceObject,你必须公开所有受保护和私有的属性TargetClass才能获得这项工作。

I use this to change FormMapper(https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Form/FormMapper.php) on the fly to IndentedFormMapperby adding a new method called chain:

我使用它来通过添加一个名为的新方法来即时更改FormMapperhttps://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Form/FormMapper.php):IndentedFormMapperchain

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