web-dev-qa-db-fra.com

Jeter l'objet actuel ($ ceci) à une classe descendante

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
34
Nathan MacInnes

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;)

9
ssice

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 ...

36
ircmaxell

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éant
  • runkit_class_emancipate - Convertir une classe héritée en classe de base, supprime toute méthode dont la portée est ancestrale

redé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.

12
Gordon

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.

2
Alan Geleynse

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;
    }
}
1
Thomas Kekeisen