将当前对象 ($this) 转换为后代类
Posted
技术标签:
【中文标题】将当前对象 ($this) 转换为后代类【英文标题】:Cast the current object ($this) to a descendent class 【发布时间】:2011-05-04 01:45:15 【问题描述】:我有一个类,可能需要将对象更改为更进一步的后代类。这可能吗?我知道一种选择是返回它的副本,但改用子类,但实际上修改当前对象会很好......所以:
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
【问题讨论】:
我有一个函数可以做到这一点,但这不是你通常应该做的事情(其他人不可能理解的代码)。实现标准工厂方法有什么问题? @Mark Baker:你能发布你的代码吗?我很好奇你是如何在不使用 runkit 之类的东西的情况下做到这一点的...... 请告诉我们您为什么要这样做 【参考方案1】:对于简单的类,这可能有效(我在极少数情况下成功使用它):
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
的类,并且您必须在 TargetClass
中公开所有受保护和私有属性才能完成这项工作。
我使用它通过添加一个名为chain
的新方法将FormMapper
(https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Form/FormMapper.php) 即时更改为IndentedFormMapper
:
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;
【讨论】:
【参考方案2】:如其他答案中所述,您可以使用讨厌的 black magic PECL 扩展来做到这一点。
不过,你真的不想要它。您想在 OOP 中解决任何问题,都可以通过符合 OOP 的方式来解决。
运行时类型层次结构修改不符合 OOP(事实上,这是有意识地避免的)。有一些设计模式应该适合您的需求。
请告诉我们你为什么要这样做,我相信一定有更好的方法来做到这一点;)
【讨论】:
谢谢。我要使用的方法是通过使父类成为子类的映射器来模拟它。听起来很乱,但我很确定我可以把它弄干净。我已经有了它应该是什么样子的想法,我正在努力将它放到电脑屏幕上。 这是我第一次听说避免了多态性并且不符合 OOP。我认为这是相反的,基本原则之一。是不是有些不同的多态性叫做“类型多态性”。 我的意思是这里的“类型交换”或者你最好想称呼它的任何东西。 OOP 方式的多态性可以使用专门的子类型代替超类型,这很简洁;但不是相反。 在 Scala 中,我所知道的唯一类型安全的方式(在某些情况下可能非常有用)(不是在 PHP 上)称为自类型。它之所以有效,是因为您可以在实际执行之前提供类型信息,因此它是类型安全的,但这可能已经有点脱离 OOP 了。 我不同意。多态性是 OOP 的关键原则之一。当然,权力越大,责任越大。【参考方案3】:在 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
免责声明:不要这样做。真的,不要。这是可能的,但这是一个可怕的想法......
【讨论】:
这个技巧是制作丑陋代码的好方法。我宁愿坚持语言提供的东西,并尝试“支持组合而不是继承”...... PHP 5.5+ 在 2013 年代仍然存在“NO CAST”问题?【参考方案4】:重新定义类
您可以使用runkit PECL extension 又名“来自地狱的工具包”来做到这一点:
runkit_class_adopt
— 将基类转换为继承类,适当时添加祖先方法
runkit_class_emancipate
— 将继承的类转换为基类,删除范围为祖先的任何方法
重新定义实例
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
。 ctor 不允许这样做,所以从技术上讲,虽然您有 Foo
的 Bar
孩子,但它不是处于有效状态。你可以添加一个神奇的__wakeup
方法来以某种方式处理这个问题,但说真的,你不希望这样,它说明了为什么铸造是一件丑陋的事情。
免责声明:我绝对不鼓励任何人在生产中使用这些解决方案。
【讨论】:
@ircmaxell 没有地狱工具包? :) 嗯,我认为这有点同情(考虑到它有多讨厌)...... @Mark 谢谢。顺便说一句,您可能想将您的问题标记给模组并询问他们是否可以永久删除它。我已经标记了它,但是当作者要求时,mods 可能更倾向于这样做。但不确定他们是否可以做到这一点。 @Gordon - 正式标记......无论如何,它从来都不是为了看到曙光的代码 - 我会更高兴知道没有足够的分数来查看已删除的答案可以选择开始使用它并在实际应用程序中使用它。我当然不会发布我的 disinherit() 反转函数(将子对象更改为其父对象)。 你知道,你给出了一个免责声明,但没有太多的理由;)【参考方案5】:这是不可能的,因为子类的实例也是父类的实例,反之则不然。
您可以做的是创建子类的新实例并将旧对象中的值复制到它上面。然后您可以返回类型为myChildClass
的新对象。
【讨论】:
以上是关于将当前对象 ($this) 转换为后代类的主要内容,如果未能解决你的问题,请参考以下文章