Llamar al cierre asignado a la propiedad del objeto directamente

109

Me gustaría poder llamar a un cierre que asigno a la propiedad de un objeto directamente sin reasignar el cierre a una variable y luego llamarlo. es posible?

El siguiente código no funciona y causa Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
Kendall Hopkins
fuente
1
Esto es exactamente lo que necesitas: github.com/ptrofimov/jslikeobject Aún más: puedes usar $ this dentro de los cierres y usar la herencia. ¡Solo PHP> = 5.4!
Renato Cuccinotto

Respuestas:

106

A partir de PHP7, puede hacer

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

o use Closure :: call () , aunque eso no funciona en un StdClass.


Antes de PHP7, tendría que implementar el __callmétodo mágico para interceptar la llamada e invocar la devolución de llamada (lo cual no es posible, por StdClasssupuesto, porque no puede agregar el __callmétodo)

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

Tenga en cuenta que no puede hacer

return call_user_func_array(array($this, $method), $args);

en el __callcuerpo, porque esto se dispararía __callen un bucle infinito.

Gordon
fuente
2
A veces encontrará esta sintaxis útil: call_user_func_array ($ this -> $ property, $ args); cuando se trata de una propiedad de clase invocable, no de un método.
Nikita Gopkalo
104

Puede hacer esto llamando a __invoke en el cierre, ya que ese es el método mágico que usan los objetos para comportarse como funciones:

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

Por supuesto, eso no funcionará si la devolución de llamada es una matriz o una cadena (que también pueden ser devoluciones de llamada válidas en PHP), solo para cierres y otros objetos con comportamiento __invoke.

Brillante
fuente
3
@marcioAlmada aunque muy feo.
Mahn
1
@Mahn Creo que es más explícito que la respuesta aceptada. Explícito es mejor en este caso. Si realmente te importa una solución "linda", call_user_func($obj->callback)no está tan mal.
marcio
Pero también call_user_funcfunciona con cadenas y eso no siempre es conveniente
Gherman
2
@cerebriform semánticamente no tiene sentido tener que hacer $obj->callback->__invoke();cuando uno esperaría que fuera $obj->callback(). Es solo una cuestión de coherencia.
Mahn
3
@Mahn: De acuerdo, no es consistente. Sin embargo, la consistencia nunca ha sido el punto fuerte de PHP. :) Pero yo creo que sí tiene sentido, si se considera la naturaleza del objeto: $obj->callback instanceof \Closure.
obispo
24

A partir de PHP 7 , puede hacer lo siguiente:

($obj->callback)();
Korikulum
fuente
Qué sentido común, pero es puramente rudo. ¡El gran poder de PHP7!
tfont
10

Desde PHP 7, se puede llamar a un cierre usando el call()método:

$obj->callback->call($obj);

Dado que PHP 7 también es posible ejecutar operaciones en (...)expresiones arbitrarias (como lo explica Korikulum ):

($obj->callback)();

Otros enfoques comunes de PHP 5 son:

  • usando el método mágico __invoke()(como lo explica Brilliand )

    $obj->callback->__invoke();
  • usando la call_user_func()función

    call_user_func($obj->callback);
  • usando una variable intermedia en una expresión

    ($_ = $obj->callback) && $_();

Cada camino tiene sus pros y sus contras, pero la solución más radical y definitiva sigue siendo la que presenta 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();
Daniele Orlando
fuente
7

Parece ser posible usar call_user_func().

call_user_func($obj->callback);

aunque no es elegante ... Lo que dice @Gordon es probablemente el único camino a seguir.

Pekka
fuente
7

Bueno, si realmente insistes. Otra solución alternativa sería:

$obj = new ArrayObject(array(),2);

$obj->callback = function() {
    print "HelloWorld!";
};

$obj['callback']();

Pero esa no es la mejor sintaxis.

Sin embargo, el intérprete PHP siempre trata T_OBJECT_OPERATOR, IDENTIFIER, (como llamada al método. Parece que no hay una solución alternativa para ->omitir la tabla de métodos y acceder a los atributos.

mario
fuente
7

Sé que esto es antiguo, pero creo que Traits maneja muy bien este problema si está utilizando PHP 5.4+

Primero, cree un rasgo que haga que las propiedades sean invocables:

trait CallableProperty {
    public function __call($method, $args) {
        if (property_exists($this, $method) && is_callable($this->$method)) {
            return call_user_func_array($this->$method, $args);
        }
    }
}

Luego, puedes usar ese rasgo en tus clases:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Ahora, puede definir propiedades a través de funciones anónimas y llamarlas directamente:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
SteveK
fuente
Wow :) Esto es mucho más elegante de lo que estaba tratando de hacer. Mi única pregunta es la misma que para los elementos del conector del grifo británico de agua caliente / fría: ¿POR QUÉ no está ya integrado?
dkellner
Gracias :). Probablemente no esté integrado debido a la ambigüedad que crea. Imagínese en mi ejemplo, si hubiera una función llamada "agregar" y una propiedad llamada "agregar". La presencia del paréntesis le dice a PHP que busque una función con ese nombre.
SteveK
2

bueno, debe enfatizarse que almacenar el cierre en una variable y llamar a la variable es en realidad (extrañamente) más rápido, dependiendo de la cantidad de la llamada, se vuelve bastante, con xdebug (medición tan precisa), estamos hablando de 1,5 (el factor, al usar una variable, en lugar de llamar directamente a __invoke. Así que en su lugar, simplemente almacene el cierre en una variable y llámelo.

Kmtdk
fuente
2

Actualizado:

$obj = new stdClass();
$obj->callback = function() {
     print "HelloWorld!";
};

PHP> = 7:

($obj->callback)();

PHP> = 5.4:

$callback = $obj->callback;  
$callback();
M Rostami
fuente
¿Intentaste esto? No funciona. Call to undefined method AnyObject::callback()(La clase AnyObject existe, por supuesto.)
Kontrollfreak
1

Aquí hay otra alternativa basada en la respuesta aceptada pero extendiendo stdClass directamente:

class stdClassExt extends stdClass {
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

Ejemplo de uso:

$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();

Probablemente sea mejor usar call_user_funco __invokeaunque.

Mahn
fuente
0

Si está utilizando PHP 5.4 o superior, puede vincular un invocable al alcance de su objeto para invocar un comportamiento personalizado. Entonces, por ejemplo, si tuviera que configurar lo siguiente ...

function run_method($object, Closure $method)
{
    $prop = uniqid();
    $object->$prop = \Closure::bind($method, $object, $object);
    $object->$prop->__invoke();
    unset($object->$prop);
}

Y estabas operando en una clase como esa ...

class Foo
{
    private $value;
    public function getValue()
    {
        return $this->value;
    }
}

Puede ejecutar su propia lógica como si estuviera operando desde dentro del alcance de su objeto

$foo = new Foo();
run_method($foo, function(){
    $this->value = 'something else';
});

echo $foo->getValue(); // prints "something else"
Lee Davis
fuente
0

Observo que esto funciona en PHP5.5

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

Permite a uno crear una colección de cierres de objetos pseudo-objetos.

Gordon Rouse
fuente