У меня есть класс, где может потребоваться изменить объект на потомственный класс дальше по строке. Это возможно? Я знаю, что один из вариантов – вернуть его копию, но вместо этого использовать дочерний класс, но было бы неплохо изменить текущий объект … так:
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
Вы можете, как описано в других ответах, сделать это с неприятными расширениями PECL черной магии .
Хотя, вы серьезно этого не хотите. Любая проблема, которую вы хотите решить в ООП, есть способ, совместимый с ООП.
Полиморфизм типа не соответствует ООП (на самом деле его сознательно избегают). Существуют шаблоны проектирования, которые должны соответствовать тем, что вы хотите.
Пожалуйста, скажите, почему вы этого хотите, я уверен, что должны быть лучшие способы сделать это;)
Кастинг, чтобы изменить тип объекта, невозможно в 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 magic …
И если вы хотите еще несколько проверок (я бы так предложил), я бы добавил эту строку в первую очередь, чтобы предотвратить проблемы:
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
Отказ от ответственности: не делайте этого. Действительно, не надо. Это возможно, но это такая ужасная идея …
Переопределение классов
Вы можете сделать это с расширением PKL runkit aka «Toolkit from Hell»:
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
, он не находится в правильном состоянии. Вы могли бы добавить волшебный метод __wakeup
чтобы справиться с этим каким-то образом, но серьезно, вы этого не хотите, и это показывает, почему кастинг – это уродливый бизнес.
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я абсолютно никому не рекомендую использовать какие-либо из этих решений в производстве.
Это невозможно, потому что, хотя экземпляр дочернего класса также является экземпляром родительского класса, обратное неверно.
Вы можете создать новый экземпляр дочернего класса и скопировать значения из старого объекта на него. Затем вы можете вернуть новый объект, который будет иметь тип myChildClass
.