Plugins


1Présentation

Il est possible de demander l'exécution de plugins, avant et/ou après l'exécution des contrôleurs de l'application. Techniquement, les plugins sont des contrôleurs possédant des méthodes spéciales, qui peuvent modifier la requête avant de la transmettre au plugin suivant ou au contrôleur désigné. On peut chaîner des plugins avant le contrôleur, et d'autres plugins après.

Les fichiers source des plugins sont stockés dans le répertoire controllers de l'application.

Un même objet peut avoir à la fois des rôles de plugin et de contrôleur.

Plugins proposés par Temma

Temma fournit plusieurs plugins, dont la documentation est accessible dans la section "Helpers" :

  • Api : Pour gérer les accès à une API
  • Cache : Pour mettre en cache les pages HTML générées
  • Langue : Gestion de la localisation multilingue

2Configuration

La configuration des pré-plugins et des post-plugins se fait en utilisant le fichier etc/temma.php (voir la documentation de la configuration). Il est possible de configurer des plugins globaux, qui seront exécutés pour toutes les requêtes :

[
    'plugins' => [
        // liste des pré-plugins */
        '_pre' => [
            'AuthenticationPlugin',
            'CmsController',
        ],
        // liste des post-plugins
        '_post' => [
            'CleanSessionPlugin'
        ]
    ]
]

Les plugins sont exécutés en suivant l'ordre dans lequel ils sont déclarés.

Il est possible de configurer des plugins qui ne s'exécuteront que pour certains contrôleurs :

[
    'plugins' => [
        // plugins exécutés systématiquement pour tous les contrôleurs
        '_pre'  => [ 'AuthenticationPlugin', 'CmsController' ],
        '_post' => [ 'CleanSessionPlugin' ],

        // plugins appelés uniquement pour le contrôleur de homepage
        'Homepage' => [
            // plugins exécutés avant le contrôleur
            '_pre' => [ 'SomePlugin', 'AnotherPlugin' ],
            // plugins exécutés après le contrôleur
            '_post' => [ 'LastPlugin' ],
        ],

        // plugins appelés uniquement pour le contrôleur d'affichage
        'Display' => [
            '_post' => [ 'CheckOutputPlugin' ]
        ]
    ]
]

Il est même possible de définir des plugins qui ne vont être appelés que pour certaines actions :

[
    'plugins' => [
        // plugins appelés uniquement pour le contrôleur de homepage
        'Homepage' => [
            // plugins toujours exécutés avant le contrôleur
            '_pre' => [ 'SomePlugin', 'AnotherPlugin' ],
            // plugins toujours exécutés après le contrôleur
            '_post' => [ 'LastPlugin' ],

            // plugins pour l'action show
            'show' => [
                // plugins exécutés avant l'action
                '_pre'  => [ 'SpecialPlugin' ],
                // plugins exécutés après l'action
                '_post' => [ 'FooPlugin' ],
            ],

            // plugins pour l'action remove
            'remove' => [
                // plugins exécutés avant l'action
                '_pre' => [ 'BarPlugin' ]
            ]
        ]
    ]
]

Il est aussi possible de définir des plugins qui vont être appelés pour tous les contrôleur sauf un. Dans ce cas, il suffit de mettre un tiret ("-") devant le nom du contrôleur :

[
    'plugins' => [
        // plugins appelés pour tous les contrôleurs
        // sauf le contrôleur d'authentification
        '-Auth' => [
            // plugins toujours exécutés avant le contrôleur
            '_pre'  => [ 'AuthPlugin' ],
            // plugins toujours exécutés après le contrôleur
            '_post' => [ 'LastPlugin' ]
        ]
    ]
]

Enfin, il est possible de définir des plugins qui vont être appelés pour toutes les actions sauf une. Là encore, il suffit de mettre un tiret ("-") devant le nom de l'action :

[
    'plugins' => [
        // plugins appelés uniquement pour le contrôleur de homepage
        'Homepage' => [
            // plugins pour l'action show
            'show' => [
                // plugins exécutés avant l'action
                '_pre' => [ 'SpecialPlugin' ],
            ],

            // plugins pour toutes les actions sauf l'action remove
            '-remove' => [
                // plugins exécutés avant l'action
                '_pre' => [ 'BarPlugin' ]
            ]
        ],

        // plugins appelés pour tous les contrôleurs,
        // sauf le contrôleur d'authentification
        '-Auth' => [
            // plugins exécutés avant n'importe quelle action
            '_pre' => [ 'FooPlugin' ],
            // plugin exécuté pour toutes les actions, sauf l'action login
            '-login' => [ 'AnotherPlugin' ]
        ]
    ]
]

3Écrire vos propres plugins

3.1Principes

Les plugins permettent de modulariser le développement d'applications web. Ils permettent notamment de factoriser des traitements qui doivent se faire systématiquement au début ou à la fin de tous les accès.

Cela peut être, par exemple, un plugin qui vérifie que certaines URLs ne sont accessible qu'aux utilisateurs authentifiés, et qui s'exécutera avant que les traitements n'atteignent les contrôleurs ; si l'utilisateur n'est pas authentifié, le plugin le redirigera vers la page d'authentification, et le contrôleur ne sera même pas exécuté.
Cela peut être un plugin qui s'exécute en fin de traitement, pour positionner des variables de template qui sont nécessaires pour toutes les pages.
Les possibilités sont infinies…

Les plugins peuvent même manipuler les données du framework, ce qui leur donne la possibilité de changer le contrôleur ou l'action qui va être exécuté, ainsi que les paramètres qui lui seront passés.


3.2Développement

Voici un exemple de plugin :

class CheckPlugin extends \Temma\Web\Plugin {
    public function plugin() {
        // on affecte une variable de template
        $this['checked'] = true;
    }
}

Comme on peut le voir dans cet exemple, les plugins héritent de la classe parente \Temma\Web\Plugin, qui dérive elle-même de la classe \Temma\Web\Controller. Les plugins sont donc des contrôleurs avec juste une capacité supplémentaire.
Un plugin peut donc être utilisé par ailleurs comme un contrôleur, et un contrôleur peut aussi être utilisé comme un plugin.

Dans cet exemple, on a défini une méthode plugin(), qui sera exécutée lorsque le plugin sera appelé. C'est donc la configuration qui déterminera si l'objet est un pré-plugin ou un post-plugin.

Pour transmettre des données entre les plugins, ou entre les plugins et le contrôleur (et inversement), il faut utiliser les variables de template. Si un plugin positionne une variable de template, elle sera accessible par tous les plugins/contrôleur qui suivent dans la chaîne d'exécution.

Pour qu'un même objet (qu'il ne soit qu'un plugin, ou contrôleur+plugin) puisse être à la fois un pré-plugin et un post-plugin, en exécutant du code différent suivant le moment de son exécution, on peut définir des méthodes preplugin() et postplugin().

  • Lorsqu'un plugin est exécuté dans la chaîne d'actions PRÉ, Temma regarde d'abord s'il possède une méthode nommée preplugin(). Si c'est le cas, elle est exécutée ; sinon, on exécute la méthode plugin().
  • Lorsqu'un plugin est exécuté dans la chaîne d'actions POST, Temma regarde d'abord s'il possède une méthode nommée postplugin(). Si c'est le cas, elle est exécutée ; sinon on exécute la méthode plugin().

Voici un exemple de contrôleur qui est capable d'agir comme pré-plugin et comme post-plugin, en plus de posséder plusieurs actions :

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

    // pré-plugin de vérification d'URL
    public function preplugin() {
        if (!$this->_checkUrl()) {
            return $this->httpError(404);
        }
        $this['checked'] = true;
    }

    // post-plugin de retraitement de variables avant envoi au template
    public function postplugin() {
        $dirtyData = $this['dirtyData'];
        $cleanData = htmlspecialchars($dirtyData);
        $this['cleanData'] = $cleanData;
    }

    // action racine
    public function index() {
        $this['pages'] = $this->_dao->search();
    }

    // action supplémentaire
    public function show($id) {
        $this['page'] = $this->_dao->get($id);
    }

    // méthode privée
    private function _checkUrl() {
        // ...
        return (true);
    }
}

3.3Manipulation des plugins et du contrôleur

Les plugins et le contrôleur peuvent modifier dynamiquement les informations que le framework utilise pour déterminer quels sont les plugins/contrôleur à exécuter.

Modification des plugins

Dans une méthode de plugin ou une méthode de contrôleur (initialisation, action ou finalisation), il est possible de récupérer la liste complète des plugins, et de la mettre à jour.

Cela se fait en passant par l'objet $this->_loader->config, qui offre l'attribut plugins, accessible en lecture et en écriture. Cet attribut contient directement le tableau de plugins tel que défini dans le fichier etc/temma.php (voir la documentation de la configuration).

Si vous modifiez la configuration des plugins, il vous faudra sûrement retourner un statut EXEC_RESTART ou EXEC_REBOOT, pour forcer Temma à réexécuter la chaîne des plugins.

Voici un exemple de pré-plugin qui inverse l'ordre des post-plugins.

class ReverseExamplePlugin extends \Temma\Web\Plugin {
    public function preplugin() {
        // récupération de la liste des plugins
        $plugins = $this->_loader->config->plugins;

        // inversion des post-plugins
        $plugins['_post'] = array_reverse($plugins['_post']);

        // mise-à-jour de la liste des plugins
        $this->_loader->config->plugins = $plugins;
    }
}
Modification du contrôleur

Dans une méthode de plugin ou une méthode de contrôleur (initialisation, action ou finalisation), il est possible d'accéder aux informations suivantes, et de les modifier :

  • Le nom du contrôleur à exécuter.
  • Le nom de l'action à exécuter.
  • Les paramètres fournis à l'action.

Toute ces informations sont accessibles par l'objet $this->_loader->request, qui propose les méthodes suivantes :

  • getController() : Retourne le nom du contrôleur qui va être exécuté. Identique à la variable de template $this['CONTROLLER'].
  • getAction() : Retourne le nom de l'action qui va être exécutée. Identique à la variable de template $this['ACTION'].
  • getNbrParams() : Retourne le nombre de paramètres.
  • getParams() : Retourne la liste des paramètres.
  • getParam(int $index, [$default]) : Retourne le paramètre dont l'index est fourni en paramètre. Une valeur par défaut peut être passée en second paramètre ; elle sera retournée si le paramètre demandé n'est pas défini.
  • setController(string $name) : Définit le nom du contrôleur à exécuter.
  • setAction(string $name) : Définit le nom de l'action à exécuter.
  • setParams(array $params) : Définit la liste des paramètres.
  • setParam(int $index, string $value) : Définit la valeur d'un paramètre dont l'index est fourni.

Attention : Si vous modifiez le contrôleur et/ou l'action avec les méthodes setController() et setAction(), pensez à mettre à jour les variables de template correspondantes $this['CONTROLLER'] et $this['ACTION'], ainsi que $this['URL'].


3.4Exemple de plugin

Voici un exemple de pré-plugin simpliste qui extrait la langue au début de l'URL.
Par exemple, pour une URL /fr/article/voir/123/mon-article, le plugin va mettre "fr" dans la variable de template $this['lang'], puis fera ce qu'il faut pour que Temma fasse comme si l'URL reçue avait été /article/voir/123/mon-article.

class LangExamplePlugin extends \Temma\Web\Plugin {
    public function preplugin() {
        // récupération de la langue
        $lang = $this['CONTROLLER'];
        // récupération du contrôleur
        $newController = $this['ACTION'];
        // récupération des paramètres
        $params = $this->_loader->request->getParams();
        // extraction de l'action et décalage des paramètres
        $newAction = array_shift($params);

        // mise-à-jour des données dans Temma
        $this->_loader->request->setController($newController);
        $this->_loader->request->setAction($newAction);
        $this->_loader->request->setParams($params);
        // mise-à-jour des variables de template
        $this['lang'] = $lang;
        $this['CONTROLLER'] = $newController;
        $this['ACTION'] = $newAction;
        $this['URL'] = substr($this['URL'], strlen($lang));
    }
}