Carlos Robles frikiblog

Controller Plugins, métodos mágicos, y funciones donde no las hay

| 0 comments

Trabajando en ZF2 estamos acostumbrados a oir hablar de plugins .
Los plugins son clases que encierran funcionalidad que queremos añadir a nuestro controlador, y permiten hacerlo de una forma escalable y modular. El concepto es muy bueno.
ZF2 cuenta con una serie de plugins predeterminados, a los que tenemos accesos siempre que extendamos AbstractActionController o AbstractRestfulController, o implementando el método setPluginManager en el controlador.
Además, podemos registrar nuestros propios plugins a traves del plugin manager. Esto ya se aleja del proposito de este post.

Hay distintas formas de acceder a los plugins. Si el controlador implementa setPluginManager(), getPluginManager() y el método plugin(), podemos conseguir cualquier plugin pasando su nombre corto a la funcion plugin() del controlador. Esta para mi es la forma más natural, y diría que la más correcta.

$plugin = $this->plugin('url');

Así, en $plugin tenemos el objeto que nos interesa, con todas las funciones y propiedades que necesitamos.

Pero la gente de Zend, que le gusta complicar las cosas para ponernoslas facil, ha creado una nueva capa por conveniencia, que nos permite acceder a las funciones del plugin sin recuperar el objeto. Una locura muy útil pero menos intuitiva.

Voy a explicar un poco esto, especialmente porque a veces puede resultar complicado seguir el código de las cosas que estamos haciendo si no tenemos claro este funcionamiento.

Para los plugins ZF2 utiliza el Overloading de PHP, que es una novedad desde la versión 5, que no es lo mismo que el overloading en otros lenguajes. En Php, Overloading permite crear dinamicamete propiedades y métodos en objetos. ¿Qué significa esto? Pues que por ejemplo podemos llamar desde el controlador a la funcion

$this->params($string)

que todos hemos usados, o siguiendo con el ejemplo anterior llamar a

 $plugin = $this->url();

cuando en realidad en ninguna parte del código de nuestro controlador o sus superclases están definidas esas funciones. Si tratamos de seguir el código para ver donde se declaran, nos podemos volver locos. Estos métodos se han creado dinamicamente.

¿Como se crean métodos dinamicamente?

Gracias a los métodos mágicos de php. En concreto, tanto AbstractActionController como AbstractRestfulController implementan el método

public function __call($name, $arguments)

Cuando una clase implementa este método, se produce la forma más mágica de overloading. Si en un objeto de esa clase, se llama a un método que no existe, la funcion __call() es llamada a cambio, recibiendo como parámetros el nombre de la funcion que fue llamada, y los parametros que se le enviaron.

Gracias a esto, cuando desde nuestro controlador llamamos a la función url, que no existe, en realidad lo que está pasando es que en el método __call, el controlador está buscando el objeto apropiado.

Pero aún hay otra vuelta te tuerca.
volvamos a nuestros ejemplos:

$this->params($string)
$plugin = $this->url();

La funcion url() creada dinamicamente, nos devuelve un objeto, pero como sabemos si hemos trabajado un poco con ZF2, vemos que lla funcion params, nos devuelve directamente el valor del parametro que le pedimos.
Esto es porque la funcion __call del controlador, hace una doble labor, primero obtiene el plugin que se llama como la funcion que se ha llamado, es decir, exactamente como si hubiesemos llamado a $plugin = $this->plugin($method), y despues, comprueba si el valor devuelto, se puede llamar directamente como si fuese una funcion, es decir, comprueba si is_callable. Si lo es, ejecuta la función, y devuelve su resultado. Si no lo es, lo devuelve como objeto.
Veamos la implementación de la funcion __call, que encontramos en Zend\Mvc\Controller\AbstractController

  /**
     * Method overloading: return/call plugins
     *
     * If the plugin is a functor, call it, passing the parameters provided.
     * Otherwise, return the plugin instance.
     *
     * @param  string $method
     * @param  array  $params
     * @return mixed
     */
 public function __call($method, $params)
    {
        $plugin = $this->plugin($method);
        if (is_callable($plugin)) {
            return call_user_func_array($plugin, $params);
        }

        return $plugin;
    }

Y para terminar…como puede ser que una función que devuelve objetos, nos esté devolviendo un callable?

Normalmente un callable es

  1. el nombre de una funcion,
  2. un objeto y un metodo,
  3. o el nombre de una clase y un metodo estático.Pues gracias a los métodos mágicos, tenemos una nueva forma de callable:
  4. Un objeto que puede ser llamado como una función. Es decir, como si hiciesmos $a=new A(); y luego llamasemos a $a(); De igual forma que hacemos con variables que contienen el nombre de una funcion, pero con la diferencia de que aqui $a no es un string, sino un objeto.

En concreto, esto lo conseguimos con el método magico __invoke, que cuando una clase lo implementa,  es llamado cuando un script intenta llamar a un objeto como si fuera una función, como hemos descrito arriba.

Por ejemplo, sabemos que el plugin params,  implementa la función  __invoke como podemos en ver en la API del plugin: http://framework.zend.com/apidoc/2.0/classes/Zend.Mvc.Controller.Plugin.Params.html

En concreto, está es la implementación, que podemos   encontrar en Zend\Mvc\Controller\Plugin\Params

/**
 * Grabs a param from route match by default.
 *
 * @param string $param
 * @param mixed $default
 * @return mixed
 */
 public function __invoke($param = null, $default = null)
 {
 if ($param === null) {
 return $this;
 }
 return $this->fromRoute($param, $default);
 }

En resumen

Cuando en nuestro controlador estamos llamando a $this->params() en realidad el método mágico __call, esta recibiendo la llamada, llamado a $this->plugin(“params”), y recibiendo un objeto $p, de la clase Params, y a continuación llamando a $p(), que en realidad está haciendo que se ejecute el metodo __invoke() del objeto $p.

Magia en cadena.

Para ver los plugins disponibles por defecto y aprender alguna cosa concreta más, podeis ir a ladocumentacion de ZF sobre los plugins

Facebook Twitter Linkedin Plusone Digg Delicious Reddit Email

Leave a Reply

Required fields are marked *.