Question

J'ai une classe où il peut être nécessaire de changer l'objet à une classe descendante plus loin sur la ligne. Est-ce possible? Je sais qu'une option est de retourner une copie de celui-ci, mais en utilisant au lieu de la classe des enfants, mais ce serait bien de modifier en fait l'objet en cours ... 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
Était-ce utile?

La solution

Vous pouvez, comme décrit dans d'autres réponses, faites-le avec méchant magie noire extensions PECL.

Bien, vous ne voulez pas sérieusement. Tout problème que vous voulez résoudre en POO il y a une manière conforme à la POO pour le faire.

modifications de la hiérarchie de type d'exécution ne sont pas conformes à la POO (en fait, cela est consciemment évité). Il existe des modèles de conception qui devrait répondre ce que vous voulez.

S'il vous plaît, dites-nous pourquoi voulez-vous, je suis sûr qu'il doit y avoir de meilleures façons de le faire;)

Autres conseils

Castings pour changer le type de l'objet est impossible en PHP (sans utiliser une extension méchant). Une fois que vous instancier un objet, vous ne pouvez pas changer la classe (ou d'autres détails de mise en œuvre) plus ...

Vous pouvez simuler avec une méthode comme ceci:

public function castAs($newClass) {
    $obj = new $newClass;
    foreach (get_object_vars($this) as $key => $name) {
        $obj->$key = $name;
    }
    return $obj;
}

Utilisation:

$obj = new MyClass();
$obj->foo = 'bar';
$newObj = $obj->castAs('myChildClass');
echo $newObj->foo; // bar

Mais méfiez-vous que cela ne change pas vraiment la classe d'origine. Il crée simplement un nouveau. Et méfiez-vous que cela nécessite que les propriétés sont publiques ou ont getter et magiques setter ...

Et si vous voulez un peu plus de chèques (je vous suggère donc), je voudrais ajouter cette ligne comme la 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 magie noire, je vais faire la même chose (en utilisant le runkit extension PECL (avertissement: ici 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);
}

Alors, au lieu de faire new Foo, vous feriez quelque chose comme ceci:

$obj = getInstance('MyClass');
echo $obj instanceof MyChildClass; //false
castAs($obj, 'myChildClass');
echo $obj instanceof MyChildClass; //true

Et à partir 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

Avertissement: Ne faites pas cela. Vraiment, ne pas. Il est possible, mais il est une idée horrible ...

Classes Redéfinir

Vous pouvez le faire avec le runkit PECL l'extension alias le " Boîte à outils de l'enfer ":

  • runkit_class_adopt - Convertir une classe de base à une classe héritée, ajouter des méthodes ancestrales, le cas échéant
  • runkit_class_emancipate - Convertir une classe héritée à une la classe de base, supprime toute méthode dont la portée est ancestrale

Les instances Redéfinir

Les fonctions runkit ne fonctionnent pas sur les instances d'objet. Si vous voulez faire sur les instances d'objets, vous pouvez théoriquement faire en déconner avec les cordes d'objet sérialisé.
Ce sont les domaines de magie noire si.

Le code ci-dessous vous permet de changer une instance de 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 voir, l'objet résultant est un objet Bar maintenant avec toutes les propriétés en conservant leur visibilité, mais prop2 est NULL. Le cteur ne marche pas permettre cela, donc techniquement, si vous avez un enfant Bar de Foo, il est pas dans un état valide. Vous pouvez ajouter une méthode __wakeup magique pour gérer cela en quelque sorte, mais sérieusement, vous ne voulez pas que et il montre pourquoi la coulée est vilaine affaire.

AVERTISSEMENT:. Je ne suis absolument pas encourager quiconque à utiliser l'une de ces solutions dans la production

Ceci est impossible car si une instance d'une classe d'enfants est aussi une instance d'une classe parente, l'inverse est pas vrai.

Ce que vous pouvez faire est de créer une nouvelle instance de la classe des enfants et copier les valeurs de l'ancien objet sur celui-ci. Vous pouvez ensuite retourner le nouvel objet qui sera de type myChildClass.

Pour les classes simples, cela peut fonctionner (je me sers de ce 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 abstrait:

$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 public-privé dans TargetClass pour obtenir ce travail.

J'utilise ce changement FormMapper ( https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/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;
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top