Appel dynamique de constructeur en PHP

Dans nos précédents articles sur les fonctions dynamiques, nous avons vu qu’il était très facile d’appeler une méthode de manière dynamique et de gérer les arguments tant lors de l’appel que dans la déclaration des méthodes.

Un type de méthode demeure toutefois particulier: le constructeur.

Instanciation dynamique

A l’instar de toute autre méthode, l’instanciation dynamique d’un objet peut passer par une variable temporaire contenant le nom de la classe à instancier.

1
2
3
4
5
class Foo{
    function __construct($a, $b){
        var_dump($a, $b);
    }
}
1
2
$className = 'Foo';
new $className('a', 'b'); //a b

Même si, à nouveau, on peut regretter la nécessité de passer par une variable temporaire…

1
2
3
4
5
class Bar{
    public static function instantiate(){
        return new get_called_class()(); //Parse error: syntax error, unexpected '('
    }
}

Espace de nom

Attention que la résolution des espaces de nom ne se fait pas en se basant sur les simplifications de l’espace de nom courant. En d’autres termes, même si vous avez un « use » qui simplifie le namespace de votre classe, vous devez, malgré tout, spécifier l’entièreté de son namespace dans la string que vous voulez évaluer. Sans cela, PHP croit qu’il s’agit d’une classe du namespace global. Le « use » n’a donc aucun impact.

1
2
3
4
5
6
7
namespace Bar;

class Foo{
    function __construct($a, $b){
        var_dump($a, $b);
    }
}
1
2
3
4
use Bar\Foo;

$className = 'Foo';
new $className('a', 'b'); //Fatal error: Class 'Foo' not found
1
2
$className = 'Bar\Foo';
new $className('a', 'b'); //a b

A noter que PHP 5.5 propose le nom de la classe de manière statique grâce à la constante magique ::CLASS, ce qui permet d’éviter de longues string…

Gestion des arguments

L’instanciation avec une liste d’arguments ne permet plus d’utiliser ce système. Malheureusement, des fonctions comme call_user_func_array() ne sont pas utilisables pour les constructeurs.

1
2
3
call_user_func_array('Foo', array('a', 'b'));
//Warning: call_user_func_array() expects parameter 1 to be a valid callback,
//function 'foo' not found or invalid function name

Il est nécessaire de passer par ReflectionClass.

1
2
$reflectedFoo = new ReflectionClass('Foo');
$reflectedFoo->newInstanceArgs(array('a', 'b')); //a b

Constructeur comme callback

Nous venons de voir que le constructeur ne peut pas servir de callback. Toutefois, on peut éventuellement passer par une closure intermédiaire.

1
2
3
$foo = function($a, $b){
    return new Foo($a, $b);
};
1
call_user_func_array($foo, array('a', 'b')); //a b

Instanciation sans appeler le constructeur

Et enfin, bien sûr, il est possible d’instancier un objet sans appeler le constructeur et ses arguments. Dit comme ça, c’est assez bizarre… Mais ça peut être utile dans certains cas, comme la création de mock par exemple.

Reflection

Depuis 5.4, on peut utiliser simplement ReflectionClass::newInstanceWithoutConstructor();

1
2
3
4
5
6
7
8
class Foo{

    public $bar;

    function __construct($bar){
        $this->bar = $bar;
    }
}
1
2
3
$reflectedFoo = new ReflectionClass('Foo');
$foo = $reflectedFoo->newInstanceWithoutConstructor();
var_dump($foo->bar); //NULL

unserialize

Avant 5.4, il existe un hack qui utilise unserialize.

1
2
3
4
5
$className = 'Foo';
$signature = 'O:' . strlen($className) . ':"' . $className . '":0:{}';

$foo = unserialize($signature);
var_dump($foo->bar); //NULL

Conlusion

Le constructeur se gère un peu différemment que les autres méthodes, mais, au final, peut être exploité avec tout autant de possibilité.

2 réflexions au sujet de « Appel dynamique de constructeur en PHP »

  1. Ping : Closure, callback et fonctions dynamiques en PHP

  2. Ping : Veille: Python, PHP, POO | Bux Blog

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>