2009-05-20 18 views
15

Este tema se expande en When do/should I use __construct(), __get(), __set(), and __call() in PHP? que habla de los métodos mágicos __construct, __get y __set.PHP 5.3 Método mágico __invoke

A partir de PHP 5.3 hay un nuevo método mágico llamado __invoke. Se llama al método __invoke cuando una secuencia de comandos intenta llamar a un objeto como una función.

Ahora en la investigación que he hecho para este método, la gente lo compara con el método de Java .run() - ver Interface Runnable.

haber pensado largo y tendido sobre esto no puedo pensar en ninguna razón por la que ustedes llamarían $obj(); en contraposición a $obj->function();

Incluso si estuviera interactuando sobre una gran variedad de objetos, a pesar de ello conocer la función principal nombre que te gustaría ejecutar

¿Es el método mágico __invoke otro ejemplo del atajo "solo porque puede, no significa que debe" en PHP, o hay casos en que esto sería realmente lo correcto?

Respuesta

22

PHP no permite el paso de punteros a funciones como otros idiomas. Las funciones no son first class en PHP. Las funciones de primera clase significan principalmente que puede guardar una función en una variable, pasarla y ejecutarla en cualquier momento.

El método __invoke es una manera que PHP puede acomodar funciones de primera clase de pseudo.

El método __invoke se puede utilizar para pasar una clase que puede actuar como closure o continuation, o simplemente como una función que puede pasar.

Mucha programación funcional se basa en funciones de primera clase. Incluso la programación imperativa normal puede beneficiarse de esto.

Supongamos que tiene una rutina de ordenación, pero desea admitir diferentes funciones de comparación. Bueno, puede tener diferentes clases de comparación que implementen la función __invoke y pasen instancias a la clase a su función de clasificación, y ni siquiera tiene que saber el nombre de la función.

Realmente, siempre pudiste haber hecho algo así como pasar una clase y tener una función llamar a un método, pero ahora casi puedes hablar sobre pasar una "función" en lugar de pasar una clase, aunque no es tan limpio como en otros idiomas.

+1

no lo entiendo ... ¿qué funciones de primera clase exactamente si no es $ a? $ a = function ($ x, $ y) {return ($ x <$ y? $ x: $ y;}; $ min = array_reduce (array (1,2,3), $ a); – stefs

+0

oooooh! okay , perdón por mezclar funciones anónimas/first class/lambda todo el tiempo. – stefs

+1

funciones de primera clase/cierres en PHP 5.3: función multiplyBy ($ x) {return function ($ y) use ($ x) {return $ x * $ y;};}.$ mulby3 = multiplyBy (3); $ twentyone = $ mulby3 (7); - __invoke es> 5.3 también, por lo que usar __invoke como reemplazo de pseudo-first-class no siempre tiene sentido. – stefs

14

Creo que esta funcionalidad existe principalmente para soportar la nueva funcionalidad de cierre de 5.3. Los cierres están expuestos como casos de la clase Closure, y son directamente invocables, p. $foo = $someClosure();. Un beneficio práctico de __invoke() es que es posible crear un tipo de devolución de llamada estándar, en lugar de utilizar combinaciones extrañas de cadenas, objetos y matrices, dependiendo de si se está haciendo referencia a una función, método de instancia o método estático.

3

Es una combinación de dos cosas. Ya has identificado correctamente uno de ellos. De hecho, es como la interfaz IRunnable de Java, donde cada objeto "ejecutable" implementa el mismo método. En Java, el método se llama run; en PHP, el método se llama __invoke, y no es necesario implementar explícitamente ningún tipo de interfaz particular de antemano.

El segundo aspecto es el azúcar sintáctica, por lo que en lugar de llamar $obj->__invoke(), puede saltarse el nombre del método, por lo que aparece como si estuviera llamando al objeto directamente: $obj().

La parte clave para que PHP tenga cierres es la primera. El lenguaje necesita algún método establecido para invocar un objeto de cierre para que haga su trabajo. El azúcar sintáctico es solo una manera de que se vea menos feo, como es el caso con todas las funciones "especiales" con prefijos de doble subrayado.

13

El uso de __invoke tiene sentido cuando necesita un callable que tiene que mantener un cierto estado interno. Digamos que desea ordenar la siguiente matriz:

$arr = [ 
    ['key' => 3, 'value' => 10, 'weight' => 100], 
    ['key' => 5, 'value' => 10, 'weight' => 50], 
    ['key' => 2, 'value' => 3, 'weight' => 0], 
    ['key' => 4, 'value' => 2, 'weight' => 400], 
    ['key' => 1, 'value' => 9, 'weight' => 150] 
]; 

La función usort le permite ordenar una matriz mediante una función, muy simple. Sin embargo, en este caso queremos ordenar la matriz usando sus paneles interiores 'value' clave, lo que podría hacerse de esta manera:

$comparisonFn = function($a, $b) { 
    return $a['value'] < $b['value'] ? -1 : ($a['value'] > $b['value'] ? 1 : 0); 
}; 
usort($arr, $comparisonFn); 

// ['key' => 'w', 'value' => 2] will be the first element, 
// ['key' => 'w', 'value' => 3] will be the second, etc 

Ahora tal vez lo que necesita para ordenar de nuevo la matriz, pero esta vez utilizando 'key' como la clave de destino , sería necesario volver a escribir la función:

usort($arr, function($a, $b) { 
    return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0); 
}); 

Como se puede ver la lógica de la función es idéntica a la anterior, sin embargo, no podemos volver a utilizar el anterior, debido a la necesidad de clasificar con una diferente llave. Este problema puede abordarse con una clase que encapsula la lógica de comparación en el método __invoke y que definen la clave para ser utilizado en su constructor:

class Comparator { 
    protected $key; 

    public function __construct($key) { 
      $this->key = $key; 
    } 

    public function __invoke($a, $b) { 
      return $a[$this->key] < $b[$this->key] ? 
       -1 : ($a[$this->key] > $b[$this->key] ? 1 : 0); 
    } 
} 

un objeto de clase que implementa __invoke se trata de un "exigible", se puede ser utilizado en cualquier contexto que una función podría ser, por lo que ahora podemos simplemente crear una instancia Comparator objetos y pasarlos a la función usort:

usort($arr, new Comparator('key')); // sort by 'key' 

usort($arr, new Comparator('value')); // sort by 'value' 

usort($arr, new Comparator('weight')); // sort by 'weight' 

los siguientes párrafos reflejan mi opinión subjetiva, por lo que si se desea se puede dejar de leer la respuesta ahora;): Aunque el ejemplo anterior mostró un uso muy interesante de __invoke, estos casos son raros y evitaría su uso, ya que se puede hacer de maneras realmente confusas y, en general, hay alternativas de implementación más simples. Un ejemplo de una alternativa en el mismo problema de clasificación sería el uso de una función que devuelve una función de comparación:

function getComparisonByKeyFn($key) { 
    return function($a, $b) use ($key) { 
      return $a[$key] < $b[$key] ? -1 : ($a[$key] > $b[$key] ? 1 : 0); 
    }; 
} 
usort($arr, getComparisonByKeyFn('weight')); 
usort($arr, getComparisonByKeyFn('key')); 
usort($arr, getComparisonByKeyFn('value')); 

Aunque este ejemplo se requiere un poco más de intimidad con lambdas | closures | anonymous functions es mucho más concisa, ya que no crea una estructura de clase completa solo para almacenar un valor externo.

+1

no podría haber pedido una mejor explicación. Gracias. –

+1

La ventaja que '__invoke' tiene en' Closure's, es que obtienes toda la estructura que proporciona una 'clase', incluida la aplicación de interfaces, la herencia y la aplicación de rasgos. Pude ver que las interfaces son lo más útil aquí. –

2

Realmente no debe llamar al $obj(); en contraposición al $obj->function(); si sabe que está tratando con cierto tipo de objeto. Dicho eso, a menos que quieras que tus colegas se rasquen la cabeza.

El método __invoke cobra vida en diferentes situaciones. Especialmente cuando se espera que proporcione un genérico invocable como argumento.

Imagine que tiene un método en una clase (que tiene que usar y no puede cambiar) que toma solo un argumento invocable.

$obj->setProcessor(function ($arg) { 
    // do something costly with the arguments 
}); 

Imagine que desea almacenar en caché y reutilizar el resultado de una operación prolongada, o acceder a los argumentos utilizados anteriormente para esa función. Con cierres regulares que pueden ser gruesos.

// say what? what is it for? 
$argList = []; 

$obj->setProcessor(function ($arg) use (&$argList) { 
    static $cache; 
    // check if there is a cached result... 
    // do something costly with the arguments 
    // remember used arguments 
    $argList[] = $arg; 
    // save result to a cache 
    return $cache[$arg] = $result; 
}); 

Véase, si sucede necesitar para acceder a la $argList de otro lugar, o simplemente limpiar la caché de las entradas estancadas, que está en problemas!

Aquí viene __invoke al rescate:

class CachableSpecificWorker 
{ 
    private $cache = []; 

    private $argList = []; 

    public function __invoke($arg) 
    { 
     // check if there is a cached result... 

     // remember used arguments 
     $this->argList[] = $arg; 

     // do something costly with the arguments 

     // save result to a cache 
     return $this->cache[$arg] = $result; 
    } 

    public function removeFromCache($arg) 
    { 
     // purge an outdated result from the cache 
     unset($this->cache[$arg]); 
    } 

    public function countArgs() 
    { 
     // do the counting 
     return $resultOfCounting; 
    } 
} 

Con la clase por encima de trabajar con los datos en caché se convierte en una brisa.

$worker = new CachableSpecificWorker(); 
// from the POV of $obj our $worker looks like a regular closure 
$obj->setProcessor($worker); 
// hey ho! we have a new data for this argument 
$worker->removeFromCache($argWithNewData); 
// pass it on somewhere else for future use 
$logger->gatherStatsLater($worker); 

Esto es solo un ejemplo simple para ilustrar el concepto. Se puede ir más allá y crear una clase genérica de envoltura y almacenamiento en caché. Y mucho más.

1

finales (en base a todo lo anterior)

Generalmente veo __invoke() {...} El método magia como una gran oportunidad para abstraer uso de la funcionalidad principal objeto de clase o para configuración intuitiva objeto (objeto preparar antes de usar sus métodos).

Caso 1 - Por ejemplo, digamos que utilizo un objeto de terceros que implementa el método mágico __invoke proporcionando así acceso fácil a la funcionalidad principal de una instancia de objeto. Para usarlo como característica principal, solo necesito saber qué parámetros espera el método __invoke y cuál sería el resultado final de esta función (cierre). De esta forma, puedo usar la funcionalidad principal del objeto de clase con muy poco esfuerzo para invertir las capacidades del objeto (tenga en cuenta que en este ejemplo no necesitamos saber ni usar ningún nombre de método).

abstracción del código real ...

en lugar de

$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2); 

ahora utilizamos:

$obj($arg1, $arg2); 

Ahora también podemos pasar un objeto de otras funciones que espera que sus parámetros se puede llamar al igual que en una función normal:

en lugar de

someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']); 

ahora usamos:

someFunctionThatExpectOneCallableArgument($someData, $obj); 

__invoke también proporciona un buen atajo uso ¿por qué no usarlo?

Cuestiones relacionadas