J'ai une classe où il peut être nécessaire de changer l'objet à une classe descendante plus loin dans la ligne. Est-ce possible? Je sais qu'une option est de le renvoyer une copie mais en utilisant la classe enfant, mais ce serait bien de modifier réellement l'objet actuel ... Donc:
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
Vous pouvez, comme décrit dans d'autres réponses, faites-le avec des extensions méchant magie noire Pecl.
Cependant, vous ne le voulez sérieusement pas. Tout problème que vous souhaitez résoudre en OOP Il y a une manière conforme à la perpétrée de l'oopie de le faire.
Les modifications de la hiérarchie de type d'exécution ne sont pas conformes au COO (en fait, cela est consciemment évité). Il y a des modèles de conception qui devraient correspondre à ce que vous voulez.
S'il vous plaît, dites-nous pourquoi voulez-vous cela, je suis sûr qu'il doit y avoir de meilleurs moyens de le faire;)
La coulée pour changer le type de l'objet n'est pas possible dans PHP (sans utiliser de prorogation désagréable). Une fois que vous avez instancié un objet, vous ne pouvez plus changer les détails de la classe (ou d'autres détails de la mise en œuvre) .. .
Vous pouvez le simuler avec une méthode comme si:
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
Mais méfiez-vous qu'il ne change pas réellement la classe d'origine. Cela crée simplement une nouvelle. Et méfiez-vous que cela nécessite que les propriétés soient publiques ou que des méthodes magiques de getter et de réglage ...
Et si vous vouliez plus de chèques (je le suggérerais), j'ajouterais cette ligne comme première ligne de castAs
pour éviter les problèmes:
if (!$newClass instanceof self) {
throw new InvalidArgumentException(
'Can\'t change class hierarchy, you must cast to a child class'
);
}
D'accord, puisque Gordon a posté une solution très magique black, je ferai la même chose (à l'aide de l'extension runkit pecl (AVERTISSEMENT: Voici des 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);
}
Donc, au lieu de faire new Foo
, tu ferais quelque chose comme ça:
$obj = getInstance('MyClass');
echo $obj instanceof MyChildClass; //false
castAs($obj, 'myChildClass');
echo $obj instanceof MyChildClass; //true
Et de l'intérieur de la classe (aussi longtemps qu'il a été créé avec getInstance
):
echo $this instanceof MyChildClass; //false
castAs($this, 'myChildClass');
echo $this instanceof MyChildClass; //true
Disclaimer: ne faites pas cela. Vraiment, pas. C'est possible, mais c'est une idée aussi horrible ...
redéfinir des cours
Vous pouvez le faire avec le runkit pecl extension AKA "Toolkit de l'enfer":
runkit_class_adopt
- Convertir une classe de base en une classe héréditaire, ajouter des méthodes ancestrales le cas échéantrunkit_class_emancipate
- Convertir une classe héritée en classe de base, supprime toute méthode dont la portée est ancestraleredéfinir les instances
Les fonctions Runkit ne fonctionnent pas sur les instances d'objet. Si vous voulez faire cela sur des instances d'objet, vous pourriez théoriquement le faire en gâterie avec les chaînes d'objets sérialisés.
[.____] C'est le domaine de Magie noire cependant.
Le code ci-dessous vous permet de modifier une instance à toute autre classe:
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), '"'), ':')
)
);
}
exemple:
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);
Résultat:
object(Bar)#3 (2) {
["prop2":protected]=>
NULL
["prop1":"Foo":private]=>
string(4) "test"
}
Comme vous pouvez le constater, l'objet résultant est un objet Bar
maintenant avec toutes les propriétés conservant leur visibilité mais prop2
est NULL
. Le CORT ne permet pas cela, donc techniquement, pendant que vous avez un Bar
enfant de Foo
, ce n'est pas dans un état valide. Vous pourriez ajouter une magie __wakeup
Méthode pour gérer cela en quelque sorte, mais sérieusement, vous ne voulez pas cela et cela montre pourquoi le casting est une entreprise laid.
Disclaimer: Je n'encourage absolument que quiconque d'utiliser aucune de ces solutions en production.
Ceci n'est pas possible car lorsqu'une instance d'une classe enfant est également une instance d'une classe mère, l'inverse n'est pas vrai.
Ce que vous pouvez faire est de créer une nouvelle instance de la classe enfant et de copier les valeurs de l'ancien objet. Vous pouvez ensuite renvoyer le nouvel objet qui sera de type myChildClass
.
Pour les classes simples, cela peut fonctionner (j'utilise cela avec succès dans certains cas rares):
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);
}
}
Utilisation dans mon cas:
$indentFormMapper = castAs($formMapper, IndentedFormMapper::class);
Plus de résumé:
$castedObject = castAs($sourceObject, TargetClass::class);
Bien sûr, TargetClass
doit hériter de la classe de sourceObject
et vous devez faire toutes les propriétés protégées et privées dans le public TargetClass
pour obtenir ce travail.
J'utilise cela pour changer FormMapper
( https://github.com/sonata-project/onataaadminbundle/form/formmapper.php ) à la volée à IndentedFormMapper
en ajoutant une nouvelle méthode appelée 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;
}
}