Plugins


1Presentación

Es posible solicitar la ejecución de plugins, antes y/o después de la ejecución de los controladores de la aplicación. Técnicamente, los plugins son controladores con métodos especiales, que pueden modificar la petición antes de pasarla al siguiente plugin o al controlador designado. Puedes encadenar plugins antes del controlador, y otros plugins después.

Los archivos fuente de los plugins se almacenan en el directorio controllers de la aplicación.

El mismo objeto puede tener tanto el rol de plugin como el de controlador.

Plugins proporcionados por Temma

Temma proporciona varios plugins, cuya documentación está disponible en la sección "Helpers":

  • Api: Para gestionar el acceso a la API
  • Cache: Para poner en caché las páginas HTML generadas
  • Debug: Para depurar fácilmente los proyectos Temma
  • Language: Gestión de la localización multilingüe
  • KebabCaseUrl: Gestión de URLs en formato “kebab-case”

2Configuración

La configuración de los pre-plugins y post-plugins se hace mediante el archivo etc/temma.php (ver la documentación de configuración). Es posible configurar plugins globales, que se ejecutarán para todas las peticiones:

<?php

return [
    'plugins' => [
        // lista de pre-plugins
        '_pre' => [
            'AuthenticationPlugin',
            'CmsController',
        ],
        // lista de post-plugins
        '_post' => [
            'CleanSessionPlugin'
        ]
    ]
];

Los plugins se ejecutan siguiendo el orden en el que se declaran.

Es posible configurar plugins que solo se ejecuten para ciertos controladores:

<?php

return [
    'plugins' => [
        // plugins ejecutados sistemáticamente para todos los controladores
        '_pre'  => [ 'AuthenticationPlugin', 'CmsController' ],
        '_post' => [ 'CleanSessionPlugin' ],

        // plugins llamados solo para el controlador homepage
        'Homepage' => [
            // plugins ejecutados antes del controlador
            '_pre'  => [ 'SomePlugin', 'AnotherPlugin' ],
            // plugins ejecutados después del controlador
            '_post' => [ 'LastPlugin' ]
        ],

        // plugins llamados solo para el controlador display
        'Display' => [
            '_post' => [ 'CheckOutputPlugin' ]
        ]
    ]
];

Incluso es posible definir plugins que solo se llamarán para ciertas acciones:

<?php

return [
    'plugins' => [
        // plugins llamados solo para el controlador homepage
        'Homepage' => [
            // plugins ejecutados antes del controlador
            '_pre'  => [ 'SomePlugin', 'AnotherPlugin' ],
            // plugins ejecutados después del controlador
            '_post' => [ 'LastPlugin' ],

            // plugins para la acción show
            'show' => [
                // plugins ejecutados antes de la acción
                '_pre'  => [ 'SpecialPlugin' ],
                // plugins ejecutados después de la acción
                '_post' => [ 'FooPlugin' ]
            ],

            // plugins para la acción remove
            'remove' => [
                // plugins ejecutados antes de la acción
                '_pre' => [ 'BarPlugin' ]
            ]
        ]
    ]
];

También es posible definir plugins que se llamarán para todos los controladores excepto uno. En este caso, basta con colocar un guion ("-") delante del nombre del controlador:

<?php

return [
    'plugins' => [
        // plugins llamados para todos los controladores excepto el controlador de autenticación
        '-Auth' => [
            // plugins siempre ejecutados antes del controlador
            '_pre'  => [ 'AuthPlugin' ],
            // plugins siempre ejecutados después del controlador
            '_post' => [ 'LastPlugin' ]
        ]
    ]
];

Finalmente, puedes definir plugins que se llamarán para todas las acciones excepto una. Aquí también, basta con colocar un guion ("-") delante del nombre de la acción:

<?php

return [
    'plugins' => [
        // plugins llamados solo para el controlador homepage
        'Homepage' => [
            // plugins para la acción show
            'show' => [
                // plugins ejecutados antes de la acción
                '_pre' => [ 'SpecialPlugin' ],
            ],

            // plugins para todas las acciones excepto la acción remove
            '-remove' => [
                // plugins ejecutados antes de la acción
                '_pre' => [ 'BarPlugin' ]
            ]
        ],

        // plugins llamados para todos los controladores excepto el controlador de autenticación
        '-Auth' => [
            // plugins ejecutados antes de cualquier acción
            '_pre'   => [ 'FooPlugin' ],
            // plugin ejecutado antes de cualquier acción, excepto la acción login
            '-login' => [ 'AnotherPlugin' ]
        ]
    ]
];

3Escribe tus propios plugins

3.1Principios

Los plugins permiten modularizar el desarrollo de aplicaciones web. Permiten en particular factorizar el procesamiento que debe hacerse sistemáticamente al principio o al final de todos los accesos.

Esto puede ser, por ejemplo, un plugin que verifique que ciertas URLs solo son accesibles para usuarios autenticados, y que se ejecutará antes de que los procesos lleguen a los controladores; si el usuario no está autenticado, el plugin lo redirigirá a la página de autenticación, y el controlador ni siquiera se ejecutará.
Puede ser un plugin que se ejecuta al final del procesamiento, para definir variables de plantilla que son necesarias para todas las páginas.
Las posibilidades son ilimitadas…

Los plugins pueden incluso manipular datos del framework, lo que les da la capacidad de cambiar el controlador o la acción que se ejecutará, así como los parámetros que se le pasarán.


3.2Desarrollo

Aquí tienes un ejemplo de plugin:

class CheckPlugin extends \Temma\Web\Plugin {
    public function plugin() {
        // definimos una variable de plantilla
        $this['checked'] = true;
    }
}

Como se puede ver en este ejemplo, los plugins heredan de la clase padre \Temma\Web\Plugin, que a su vez deriva de la clase \Temma\Web\Controller. Los plugins son por tanto controladores con solo una capacidad adicional.
Un plugin puede por tanto también usarse como controlador, y un controlador puede también usarse como plugin.

En este ejemplo, hemos definido un método plugin(), que se ejecutará cuando se llame al plugin. Es por tanto la configuración la que determinará si el objeto es un pre-plugin o un post-plugin.

Para transmitir datos entre plugins, o entre plugins y el controlador (y viceversa), hay que usar variables de plantilla. Si un plugin define una variable de plantilla, será accesible para todos los plugins/controladores que sigan en la cadena de ejecución.

Para que el mismo objeto (ya sea solo un plugin, o controlador+plugin) pueda ser tanto pre-plugin como post-plugin, ejecutando código diferente según el momento de su ejecución, podemos definir métodos preplugin() y postplugin().

  • Cuando un plugin se ejecuta en la cadena de acciones PRE, Temma primero comprueba si tiene un método llamado preplugin(). Si es así, se ejecuta; si no, se ejecuta el método plugin().
  • Cuando un plugin se ejecuta en la cadena de acciones POST, Temma primero comprueba si tiene un método llamado postplugin(). Si es así, se ejecuta; si no, se ejecuta el método plugin().

Aquí tienes un ejemplo de controlador capaz de actuar como pre-plugin y como post-plugin, además de tener varias acciones:

class Page extends \Temma\Web\Plugin {
    protected $_temmaAutoDao = true;

    // pre-plugin de verificación de URL
    public function preplugin() {
        if (!$this->_checkUrl()) {
            return $this->_httpError(404);
        }
        $this['checked'] = true;
    }

    // post-plugin para reprocesar variables antes de enviarlas a la plantilla
    public function postplugin() {
        $dirtyData = $this['dirtyData'];
        $cleanData = htmlspecialchars($dirtyData);
        $this['cleanData'] = $cleanData;
    }

    // acción raíz
    public function index() {
        $this['pages'] = $this->_dao->search();
    }

    // acción adicional
    public function show($id) {
        $this['page'] = $this->_dao->get($id);
    }

    // método privado
    private function _checkUrl() {
        // ...
        return (true);
    }
}

3.3Gestión de los plugins y del controlador

Los plugins y el controlador pueden modificar dinámicamente la información que usa el framework para determinar qué plugins/controlador ejecutar.

Modificación de los plugins

En un método de plugin o un método de controlador (inicialización, acción o finalización), es posible recuperar la lista completa de plugins, y actualizarla.

Esto se hace a través del objeto $this->_loader->config, que ofrece el atributo plugins, legible y modificable. Este atributo contiene directamente el array de plugins tal como se define en el archivo etc/temma.php (ver la documentación de configuración).

Si cambias la configuración de los plugins, puede que necesites devolver un estado EXEC_RESTART o EXEC_REBOOT, para forzar a Temma a volver a ejecutar la cadena de plugins.

Aquí tienes un ejemplo de pre-plugin que invierte el orden de los post-plugins.

class ReverseExamplePlugin extends \Temma\Web\Plugin {
    public function preplugin() {
        // recupera la lista de plugins
        $plugins = $this->_loader->config->plugins;

        // inversión de los post-plugins
        $plugins['_post'] = array_reverse($plugins['_post']);

        // actualiza la lista de plugins
        $this->_loader->config->plugins = $plugins;
    }
}
Modificación del controlador

En un método de plugin o un método de controlador (inicialización, acción o finalización), es posible acceder a la siguiente información, y modificarla:

  • El nombre del controlador a ejecutar.
  • El nombre de la acción a realizar.
  • Los parámetros proporcionados a la acción.

Se puede acceder a toda esta información mediante el objeto $this->_loader->request, que ofrece los siguientes métodos:

  • getController(): Devuelve el nombre del controlador que se ejecutará. Idéntico a la variable de plantilla $this['CONTROLLER'].
  • getAction(): Devuelve el nombre de la acción que se ejecutará. Idéntico a la variable de plantilla $this['ACTION'].
  • getParamCount(): Devuelve el número de parámetros.
  • getParams(): Devuelve la lista de parámetros.
  • getParam(int $index, [$default]): Devuelve el parámetro cuyo índice se indica como parámetro. Se puede pasar un valor por defecto como segundo parámetro; se devolverá si el parámetro solicitado no está definido.
  • setController(string $name): Define el nombre del controlador a ejecutar.
  • setAction(string $name): Define el nombre de la acción a ejecutar.
  • setParams(array $params): Define la lista de parámetros.
  • setParam(int $index, string $value): Define el valor de un parámetro cuyo índice se proporciona.

Advertencia: Si modificas el controlador y/o la acción con los métodos setController() y setAction(), recuerda actualizar las variables de plantilla correspondientes $this['CONTROLLER'] y $this['ACTION'], así como $this['URL'].


3.4Ejemplo de plugin

Aquí tienes un ejemplo de un pre-plugin simplista que extrae el idioma al principio de la URL.
Por ejemplo, para una URL /en/article/show/123/my-article, el plugin pondrá "en" en la variable de plantilla $this['lang'], y luego hará lo necesario para que Temma actúe como si la URL recibida hubiera sido /article/show/123/my-article.

class LangExamplePlugin extends \Temma\Web\Plugin {
    public function preplugin() {
        // recupera el idioma
        $lang = $this['CONTROLLER'];
        // recupera el controlador
        $newController = $this['ACTION'];
        // recupera los parámetros
        $params = $this->_loader->request->getParams();
        // extrae la acción y desplaza los parámetros
        $newAction = array_shift($params);

        // actualiza los datos en Temma
        $this->_loader->request->setController($newController);
        $this->_loader->request->setAction($newAction);
        $this->_loader->request->setParams($params);
        // actualiza las variables de plantilla
        $this['lang'] = $lang;
        $this['CONTROLLER'] = $newController;
        $this['ACTION'] = $newAction;
        $this['URL'] = mb_substr($this['URL'], mb_strlen($lang) + 1);
    }
}