Je voudrais pouvoir appeler une fermeture que j'attribue directement à la propriété d'un objet sans réaffecter la fermeture à une variable, puis l'appeler. Est-ce possible?
Le code ci-dessous ne fonctionne pas et provoque Fatal error: Call to undefined method stdClass::callback()
.
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
Depuis PHP7, vous pouvez faire
$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');
ou utilisez Closure :: call () , bien que cela ne fonctionne pas sur un StdClass
.
Avant PHP7, vous devez implémenter la magie __call
méthode pour intercepter l'appel et appeler le rappel (ce qui n'est pas possible pour StdClass
bien sûr, car vous ne pouvez pas ajouter le __call
méthode)
class Foo
{
public function __call($method, $args)
{
if(is_callable(array($this, $method))) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
}
$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');
Notez que vous ne pouvez pas faire
return call_user_func_array(array($this, $method), $args);
dans le __call
body, car cela déclencherait __call
dans une boucle infinie.
Vous pouvez le faire en appelant __invoke à la fermeture, car c'est la méthode magique que les objets utilisent pour se comporter comme des fonctions:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
Bien sûr, cela ne fonctionnera pas si le rappel est un tableau ou une chaîne (qui peut également être des rappels valides en PHP) - juste pour les fermetures et autres objets avec un comportement __invoke.
Depuis ( PHP 7 vous pouvez faire ce qui suit:
($obj->callback)();
Puisque PHP 7 une fermeture peut être appelée en utilisant la méthode call()
:
$obj->callback->call($obj);
Puisque PHP 7 il est également possible d'exécuter des opérations sur des expressions arbitraires (...)
(Comme expliqué par Korikulum ):
($obj->callback)();
D'autres approches courantes PHP 5 sont:
en utilisant la méthode magique __invoke()
(comme expliqué par Brilliand )
$obj->callback->__invoke();
en utilisant la fonction call_user_func()
call_user_func($obj->callback);
en utilisant une variable intermédiaire dans une expression
($_ = $obj->callback) && $_();
Chaque voie a ses avantages et ses inconvénients, mais la solution la plus radicale et définitive reste celle présentée par Gordon .
class stdKlass
{
public function __call($method, $arguments)
{
// is_callable([$this, $method])
// returns always true when __call() is defined.
// is_callable($this->$method)
// triggers a "PHP Notice: Undefined property" in case of missing property.
if (isset($this->$method) && is_callable($this->$method)) {
return call_user_func($this->$method, ...$arguments);
}
// throw exception
}
}
$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();
Cela semble possible en utilisant call_user_func()
.
call_user_func($obj->callback);
pas élégant, cependant .... Ce que dit @Gordon est probablement la seule voie à suivre.
Eh bien, si vous vraiment insistez. Une autre solution serait:
$obj = new ArrayObject(array(),2);
$obj->callback = function() {
print "HelloWorld!";
};
$obj['callback']();
Mais ce n'est pas la plus belle syntaxe.
Cependant, l'analyseur PHP traite toujours T_OBJECT_OPERATOR
, IDENTIFIER
, (
comme appel de méthode. Il ne semble pas y avoir de solution pour faire ->
contourne la table de méthodes et accède aux attributs à la place.
Je sais que c'est vieux, mais je pense que Traits gère bien ce problème si vous utilisez PHP 5.4+
Tout d'abord, créez un trait qui rend les propriétés appelables:
trait CallableProperty {
public function __call($method, $args) {
if (property_exists($this, $method) && is_callable($this->$method)) {
return call_user_func_array($this->$method, $args);
}
}
}
Ensuite, vous pouvez utiliser ce trait dans vos classes:
class CallableStdClass extends stdClass {
use CallableProperty;
}
Maintenant, vous pouvez définir des propriétés via des fonctions anonymes et les appeler directement:
$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
Voici une autre façon d'appeler avec succès des propriétés d'objet en tant que fermeture.
Lorsque vous ne souhaitez pas modifier l'objet principal, utilisez ceci:
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
return function () {
print "HelloWorld!";
};
};
$obj->callback();
MISE À JOUR:
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
print "HelloWorld!";
};
$callback = $obj->callback;
$callback();
eh bien, il faut souligner que le stockage de la fermeture dans une variable, et l'appel de la variable est en fait (bizarrement) plus rapide, selon le montant de l'appel, cela devient beaucoup, avec xdebug (donc une mesure très précise), nous parlons 1,5 (le facteur, en utilisant une variable, au lieu d'appeler directement le __invoke. Donc au lieu de cela, il suffit de stocker la fermeture dans une variable et de l'appeler.
Voici une autre alternative basée sur la réponse acceptée mais en étendant directement stdClass:
class stdClassExt extends stdClass {
public function __call($method, $args)
{
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
Exemple d'utilisation:
$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();
Il vaut probablement mieux utiliser call_user_func
ou __invoke
bien que.
Si vous utilisez PHP 5.4 ou supérieur, vous pouvez lier un appelable à la portée de votre objet pour invoquer un comportement personnalisé. Par exemple, si vous deviez configurer ce qui suit ..
function run_method($object, Closure $method)
{
$prop = uniqid();
$object->$prop = \Closure::bind($method, $object, $object);
$object->$prop->__invoke();
unset($object->$prop);
}
Et vous opériez sur une classe comme ça ..
class Foo
{
private $value;
public function getValue()
{
return $this->value;
}
}
Vous pouvez exécuter votre propre logique comme si vous fonctionniez dans le cadre de votre objet
$foo = new Foo();
run_method($foo, function(){
$this->value = 'something else';
});
echo $foo->getValue(); // prints "something else"