我有一个类,可能需要将对象更改为后续级别的后代类。这可能吗?我知道一个选项是返回它的副本但是改为使用子类,但实际上修改当前对象会很好...所以:
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
答案 0 :(得分:33)
在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'
);
}
好吧,既然戈登发布了一个非常黑魔法的解决方案,我会做同样的事情(使用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
免责声明:不要这样做。真的,不要。这是可能的,但这是一个如此可怕的想法......
答案 1 :(得分:11)
重新定义课程
您可以使用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不允许这样做,所以从技术上讲,当你有一个Bar
孩子Foo
时,它不是有效状态。你可以添加一个神奇的__wakeup
方法以某种方式处理这个问题,但是说真的,你不想要那个,它显示了为什么演员是丑陋的事情。
免责声明:我绝对不鼓励任何人在制作中使用这些解决方案。
答案 2 :(得分:9)
如其他答案中所述,您可以使用令人讨厌的黑魔法 PECL扩展程序来执行此操作。
尽管如此,你真的不想要它。您想要在OOP中解决的任何问题都有符合OOP标准的方法。
运行时类型层次结构修改不符合OOP(事实上,这是有意识地避免的)。有些设计模式应该符合您的要求。
请告诉我们您为什么要这样做,我相信必须有更好的方法来实现它;)
答案 3 :(得分:2)
这是不可能的,因为虽然子类的实例也是父类的实例,但反之则不然。
您可以做的是创建子类的新实例,并将旧对象中的值复制到其上。然后,您可以返回类型为myChildClass
的新对象。
答案 4 :(得分: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
中将所有受保护的私有属性公开才能获得这项工作。
我使用它通过添加名为FormMapper
的新方法,将IndentedFormMapper
(https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Form/FormMapper.php)即时更改为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;
}
}