Внесите текущий объект ($ this) в класс потомков

У меня есть класс, где может потребоваться изменить объект на потомственный класс дальше по строке. Это возможно? Я знаю, что один из вариантов – вернуть его копию, но вместо этого использовать дочерний класс, но было бы неплохо изменить текущий объект … так:

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 .