PHP 5.3 Método mágico__invocar
oop magic-methods (6)
Concluyendo (basado en todo lo anterior)
Generalmente veo el método mágico __invoke () {...} como una gran oportunidad para abstraer el uso de la funcionalidad principal del objeto de clase o para la configuración intuitiva del objeto (preparar el objeto antes de usar sus métodos).
Caso 1: por ejemplo, digamos que uso un objeto de terceros que implementa el método mágico de invocación, lo que proporciona un acceso fácil a la funcionalidad principal de una instancia de objeto. Para usar la función principal, solo necesito saber qué parámetros espera el método de invocación y cuál sería el resultado final de esta función (cierre). De esta manera, puedo usar la funcionalidad principal del objeto de clase con 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).
Resumen del código real ...
en lugar de
$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);
ahora usamos:
$obj($arg1, $arg2);
Ahora también podemos pasar un objeto a otras funciones que esperan que sus parámetros sean invocables como en una función normal:
en lugar de
someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), ''someFunctionNameInitTheMainFunctionality'']);
ahora usamos:
someFunctionThatExpectOneCallableArgument($someData, $obj);
__invoke también proporciona un método abreviado de uso agradable, ¿por qué no usarlo?
Este tema se expande sobre ¿ Cuándo debo / debo usar __construct (), __get (), __set () y __call () en PHP? que habla de los métodos mágicos __construct
, __get
y __set
.
A partir de PHP 5.3 hay un nuevo Método Mágico llamado __invoke
. El método __invoke
se llama cuando un script intenta llamar a un objeto como una función.
Ahora en la investigación que he hecho para este método, la gente lo .run()
método Java .run()
- vea Interface Runnable .
Habiendo pensado mucho sobre esto, no puedo pensar en ninguna razón por la que llamaría a $obj();
a diferencia de $obj->function();
Incluso si estuviera iterando sobre una matriz de objetos, aún sabría el nombre de la función principal que desearía ejecutar.
Entonces, ¿el método mágico __invoke
es otro ejemplo de ''solo porque puedes, no significa que __invoke
en PHP, o hay casos en los que esto sería lo correcto?
Creo que esta funcionalidad existe principalmente para soportar la nueva funcionalidad de cierre 5.3. Los cierres se exponen como instancias de la clase Closure
y son invocables directamente, por ejemplo, $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 usar combinaciones extrañas de cadenas, objetos y matrices dependiendo de si está haciendo referencia a una función, método de instancia o método estático.
El uso de __invoke
tiene sentido cuando se necesita una callable que debe mantener un estado interno. Digamos que quiere 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 permite ordenar una matriz usando alguna función, muy simple. Sin embargo, en este caso, queremos ordenar la matriz utilizando ''value''
clave ''value''
su matriz interna, 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 necesite ordenar la matriz de nuevo, pero esta vez usando ''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 puede ver, la lógica de la función es idéntica a la anterior, sin embargo, no podemos reutilizar la anterior debido a la necesidad de clasificar con una clave diferente. Este problema se puede abordar con una clase que encapsula la lógica de comparación en el método __invoke
y que define la clave que se usará 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
es un "llamable", se puede usar en cualquier contexto que una función pueda ser, por lo que ahora podemos simplemente instanciar los objetos del Comparator
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, así que si quieres puedes 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 en Formas realmente confusas y generalmente 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 requiere un poco más de intimidad con las lambdas | cierres | Las funciones anónimas son mucho más concisas, ya que no crean una estructura de clase completa solo para almacenar un valor externo.
Es una combinación de dos cosas. Ya has identificado correctamente uno de ellos. De hecho, esto es igual que 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 en particular de antemano.
El segundo aspecto es el azúcar sintáctico , por lo que en lugar de llamar a $obj->__invoke()
, puede omitir el nombre del método, por lo que parece que está llamando directamente al objeto: $obj()
.
La parte clave para que PHP tenga cierres es la primera. El lenguaje necesita algún método establecido para llamar a un objeto de cierre para que haga su trabajo. El azúcar sintáctico es solo una forma de hacerlo menos feo, como es el caso de todas las funciones "especiales" con prefijos de subrayado doble.
PHP no permite el paso de punteros de función como otros idiomas. Las funciones no son de primera clase en PHP. Las funciones que son de primera clase principalmente significa que puede guardar una función en una variable, y pasarla y ejecutarla en cualquier momento.
El método __invoke
es una forma en que PHP puede acomodar funciones de pseudo-primera clase.
El método __invoke
se puede usar para pasar una clase que puede actuar como un closure o una continuation , o simplemente como una función que puede pasar.
Una gran cantidad de programación funcional se basa en funciones de primera clase. Incluso la programación imperativa normal puede beneficiarse de esto.
Digamos que tenía una rutina de clasificación, pero quería admitir diferentes funciones de comparación. Bueno, puede tener diferentes clases de comparación que implementan la función __invoke y pasan 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 podría haber hecho algo como pasar una clase y hacer que una función llame a un método, pero ahora casi puede hablar de pasar una "función" en lugar de pasar una clase, aunque no es tan limpio como en otros idiomas.
Realmente no deberías llamar a $obj();
a diferencia de $obj->function();
Si sabes que estás tratando con un determinado tipo de objeto. Dicho esto, 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 reclamo genérico como argumento.
Imagina que tienes un método en una clase (que tienes que usar y no puedes cambiar) que solo toma un llamable como argumento.
$obj->setProcessor(function ($arg) {
// do something costly with the arguments
});
Ahora 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;
});
Vea, si necesita acceder a la lista $argList
desde otro lugar, o simplemente limpie la caché de entradas bloqueadas, ¡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 anterior, 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);
Este es solo un ejemplo simple para ilustrar el concepto. Uno puede ir aún más lejos y crear un contenedor genérico y una clase de almacenamiento en caché. Y mucho más.