A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

5.11. Ecrire son propre type de route

Bien que ZF3 vous fournisse de nombreux types de routes, dans certaines situations, vous devrez écrire votre propre type de route.

Un exemple du besoin d'un type de route personnalisé est lorsque vous devez définir des règles de mappage d'URL dynamiquement. Habituellement, vous stockez la configuration de routage dans le fichier de configuration du module, mais dans certains systèmes CMS, vous aurez des documents stockés dans la base de données. Pour un tel système, vous devez développer un type d'itinéraire personnalisé qui se connecte à la base de données et effectue une correspondance d'itinéraire avec les données stockées dans la base de données. Vous ne pouvez pas stocker ces informations dans le fichier de configuration, car les nouveaux documents sont créés par les administrateurs système et non par des développeurs.

5.11.1. RouteInterface

Nous savons que chaque classe de route doit implémenter l'interface Zend\Router\Http\RouteInterface. Les méthodes de cette interface sont présentées dans le tableau 5.4 :

Table 5.4. RouteInterface methods
Nom de la méthode Description
factory($options) Méthode statique pour la création de la classe de routage.
match($request) Méthode qui effectue une correspondance avec les données de requête HTTP.
assemble($params, $options) Méthode de génération d'URL par paramètres de route.
getAssembledParams() Méthode permettant de récupérer les paramètres utilisés pour la génération d'URL.

La méthode statique factory() est utilisée par le routeur ZF3 (TreeRouteStack ou SimpleRouteStack) pour instancier la classe de routage. Le routeur passe le tableau options en argument pour la méthode factory().

La méthode match() est utilisée pour effectuer la mise en correspondance de la requête HTTP (plus particulierement de son URL) avec les données d'options transmises à la classe route via factory(). La méthode match() doit retourner une instance de la classe RouteMatch en cas de correspondance réussie ou null en cas d'échec.

La méthode assemble() est utilisée pour générer une URL par paramètres et options de route. Le but de l'aide de vue getAssembledParams() est de renvoyer le tableau de paramètres qui a été utilisé pour la génération de l'URL.

5.11.2. Classe de route personnalisée

Pour illustrer la création d'un type de route personnalisé, améliorons notre approche précédente de création du système de documentation simple avec le type de route Regex. L'inconvénient du type de route Regex est que vous ne pouvez pas organiser les pages statiques dans une hiérarchie en créant des sous-répertoires sous le répertoire doc (lorsque vous générez une URL pour une telle page, le séparateur barre oblique ('/') sera encodé et l'URL inutlisable). Nous allons donc créer une classe StaticRoute personnalisée qui permet de résoudre ce problème.

La classe que nous allons créer sera plus puissante car elle ne reconnaîtra pas seulement les URL commençant par "/doc" et se terminant par ".html". Eelle reconnaîtra aussi les URL génériques, comme "/help" ou "/support/chapter1/introduction".

Ce que nous voulons :

Pour commencer, créez le sous-répertoire Route dans le répertoire source du module et placez-y le fichier StaticRoute.php (figure 5.9).

Figure 5.9. Le fichier StaticRoute.php Figure 5.9. Le fichier StaticRoute.php

Dans ce fichier, collez le bout de code présenté ci-dessous :

<?php
namespace Application\Route;

use Traversable;
use \Zend\Router\Exception;
use \Zend\Stdlib\ArrayUtils;
use \Zend\Stdlib\RequestInterface as Request;
use \Zend\Router\Http\RouteInterface;
use \Zend\Router\Http\RouteMatch;

// Route personnalisée qui déssert les pages web "statiques".
class StaticRoute implements RouteInterface
{
    // Crée une nouvelle route avec des options données.
    public static function factory($options = []) 
    {
    }

    // Correspond à une requête donnée.
    public function match(Request $request, $pathOffset = null) 
    {
    }

    // Assemble une URL par ses paramètres de route.
    public function assemble(array $params = [], array $options = []) 
    {
    }

    // Récupère la liste des paramètres utilisés lors de l'assemblage.
    public function getAssembledParams() 
    {    
    }
}

Dans l'exemple ci-dessus, vous pouvez voir que nous avons placé la classe StaticRoute dans l'espace de noms Application\Route (ligne 2).

Aux lignes 4-9, nous définissons des alias de nom de classe pour raccourcir leurs noms.

Avec lignes 12-33, nous définissons le code pour la classe StaticRoute. La classe StaticRoute implémente l'interface RouteInterface et définit toutes les méthodes spécifiées par l'interface: factory(), match(), assemble() et getAssembledParams().

Ensuite, ajoutons plusieurs propriétés protégées et la méthode constructeur de la classe StaticRoute, comme indiqué ci-dessous :

<?php
//...

class StaticRoute implements RouteInterface
{
    // Répertoire de base des vues.
    protected $dirName;
    
    // Préfixe du chemin vers les vues.
    protected $templatePrefix;

    // Modèle de nom de fichier.
    protected $fileNamePattern = '/[a-zA-Z0-9_\-]+/';
    
    // Valeurs par défaut
    protected $defaults;

    // Liste des paramètres assemblés.
    protected $assembledParams = [];
  
    // Constructeur.
    public function __construct($dirName, $templatePrefix, 
            $fileNamePattern, array $defaults = [])
    {
        $this->dirName = $dirName;
        $this->templatePrefix = $templatePrefix;
        $this->fileNamePattern = $fileNamePattern;
        $this->defaults = $defaults;
    }
  
    // ...
}

Ci-dessus, à la ligne 7, nous définissons la propriété $dirName qui est destinée à stocker le nom du répertoire de base où seront situés les vues "statiques". A la ligne 10, nous définissons la variable de classe $templatePrefix pour stocker le préfixe à ajouter à tous les noms de vues. La ligne 13 contient la variable $fileNamePattern qui sera utilisée pour vérifier le nom du fichier.

Lignes 22-29, nous définissons la méthode constructeur appelée lors de la création de l'instance pour initialiser les propriétés protégées.

Implémentons ensuite la méthode factory() pour notre classe de route personnalisée StaticRoute. La méthode factory() sera appelée par le routeur pour instancier la classe route :

<?php
//...

class StaticRoute implements RouteInterface
{
    //...
  
    // Crée une nouvelle route avec les options données
    public static function factory($options = [])
    {
        if ($options instanceof Traversable) {
            $options = ArrayUtils::iteratorToArray($options);
        } elseif (!is_array($options)) {
            throw new Exception\InvalidArgumentException(__METHOD__ . 
                ' expects an array or Traversable set of options');
        }

        if (!isset($options['dir_name'])) {
            throw new Exception\InvalidArgumentException(
                'Missing "dir_name" in options array');
        }
	
        if (!isset($options['template_prefix'])) {
            throw new Exception\InvalidArgumentException(
                'Missing "template_prefix" in options array');
        }
	
        if (!isset($options['filename_pattern'])) {
            throw new Exception\InvalidArgumentException(
                'Missing "filename_pattern" in options array');
        }
			
        if (!isset($options['defaults'])) {
            $options['defaults'] = [];
        }

        return new static(
            $options['dir_name'], 
            $options['template_prefix'], 
            $options['filename_pattern'], 
            $options['defaults']);
    }  
}

Dans le code ci-dessus, nous voyons que la méthode factory() prend le tableau options comme argument (ligne 9). Le tableau options peut contenir les options de configuration de la classe de route. La classe StaticRoute acceptera les options suivantes:

Une fois que nous avons analysé les options, dans les lignes 37-41, nous appelons le constructeur de la classe pour instancier et retourner l'objet StaticRoute.

La méthode suivante que nous ajoutons à la classe StaticRoute est la méthode match() :

<?php
//...

class StaticRoute implements RouteInterface
{
    //...

    // Vérifie la correspondance avec une requête donnée.
    public function match(Request $request, $pathOffset=null)
    {
        // S'assure que ce type de route est utilisé dans une requête HTTP
        if (!method_exists($request, 'getUri')) {
            return null;
        }

        // Récupère l'URL et sa partie chemin.
        $uri  = $request->getUri();
        $path = $uri->getPath();
	
        if($pathOffset!=null) 
            $path = substr($path, $pathOffset);
	 
        // Récupère le tableau des segments de chemin.
        $segments = explode('/', $path);
			
        // Vérifie chaque segment par rapport au modèle des noms de fichier autorisé.
        foreach ($segments as $segment) {            
            if(strlen($segment)==0)
                continue;
            if(!preg_match($this->fileNamePattern, $segment))
            return null;
        }
	
        // Vérifie si le fichier .phtml existe sur le disque
        $fileName = $this->dirName . '/'. 
                $this->templatePrefix.$path.'.phtml';                
        if(!is_file($fileName) || !is_readable($fileName)) {
            return null;
        }
			
        $matchedLength = strlen($path); 
	
        // Prépare l'objet RouteMatch
        return new RouteMatch(array_merge(
              $this->defaults, 
              ['page'=>$this->templatePrefix.$path]
             ), 
             $matchedLength);
    }
}

Dans le code ci-dessus, nous voyons que la méthode match() prend deux arguments : l'objet de requête HTTP (une instance de Zend\Stdlib\Request) et le paramètre offset du chemin d'URL. L'objet de requête est utilisé pour accéder à la requete URL (ligne 17). Le paramètre offset du chemin est un entier non négatif, qui pointe vers la partie de l'URL avec laquelle la route est comparée (ligne 21).

À la ligne 24, nous extrayons les segments de l'URL. Ensuite, nous vérifions si chaque segment est un nom de fichier (répertoire) acceptable (lignes 27-32). Si le segment n'est pas un nom de fichier valide, nous renvoyons null comme code d'échec.

A la ligne 35, nous calculons le chemin vers la vue, et aux lignes 37-39 nous vérifions si un tel fichier existe réellement et est accessible à la lecture. De cette façon, nous faisons correspondre l'URL à la structure du répertoire.

Aux lignes 44-48, nous préparons et renvoyons l'objet RouteMatch avec les paramètres par défaut et le paramètre "page" contenant le nom de la vue pour le rendu.

Pour compléter l'implémentation de notre classe StaticRoute, nous ajoutons les méthodes assemble() et getAssembledParams() qui seront utilisées pour la génération d'URL par les paramètres de route. Le code de ces méthodes est présenté ci-dessous :

<?php
//...

class StaticRoute implements RouteInterface
{
    //...

    // AAssemble une URL par des paramètres de route
    public function assemble(array $params = [], 
                           array $options = [])
    {
        $mergedParams = array_merge($this->defaults, $params);
        $this->assembledParams = [];
	
        if(!isset($params['page'])) {
            throw new Exception\InvalidArgumentException(__METHOD__ . 
               ' expects the "page" parameter');
        }
	
        $segments = explode('/', $params['page']);
        $url = '';
        foreach($segments as $segment) {
            if(strlen($segment)==0)
                continue;
            $url .= '/' . rawurlencode($segment);
        }
	
        $this->assembledParams[] = 'page';
	
        return $url;
    }

    // Récupère la liste des paramètres utilisés lors de l'assemblage.
    public function getAssembledParams()
    {
        return $this->assembledParams;
    }
}

Dans le code ci-dessus, nous définissons la méthode assemble(), qui prend deux arguments : le tableau parameters et le tableau options (ligne 9). La méthode construit l'URL en encodant chaque segments et en les concaténant (ligne 20-26).

La méthode getAssembledParams() renvoie simplement les noms des paramètres que nous avons utilisés pour générer l'URL (ligne 36).

Nous avons maintenant terminé notre classe StaticRoute. Pour utiliser notre type de route personnalisé, nous ajoutons la configuration suivante au fichier de configuration module.config.php :

'static' => [
    'type' => StaticRoute::class,
    'options' => [
        'dir_name'         => __DIR__ . '/../view',
        'template_prefix'  => 'application/index/static',
        'filename_pattern' => '/[a-z0-9_\-]+/',
        'defaults' => [
            'controller' => Controller\IndexController::class,
            'action'     => 'static',
        ],                    
    ],
],

A la ligne 1 de la configuration ci-dessus, nous déclarons la règle de routage "static". Le paramètre type définit le nom complet de la classe StaticRoute (ligne 2). Dans le tableau options, nous définissons le répertoire de base où seront placées les pages "statiques" (ligne 4), le préfixe des vues (ligne 5), le pattern du nom de fichier (ligne 6) et le tableau defaults contenant le nom du contrôleur et l'action qui servira toutes les pages statiques.

N'oubliez pas d'insérer la ligne suivante au début de la classe module.config.php :

use Application\Route\StaticRoute;

La dernière étape consiste à créer la méthode d'action corresponndante dans la classe IndexController :

public function staticAction() 
{
    // On récupère le chemin d'accès aux vues à partir des paramètres de route
    $pageTemplate = $this->params()->fromRoute('page', null);
    if($pageTemplate==null) {
        $this->getResponse()->setStatusCode(404); 
        return;
    }
	
    // On effectue le rendu de la page
    $viewModel = new ViewModel([
            'page'=>$pageTemplate
        ]);
    $viewModel->setTemplate($pageTemplate);
    return $viewModel;
}

L'action ci-dessus est presque identique à l'action que nous avons utilisée pour la route Regex. À la ligne 4, nous récupérons le paramètre de route page et l'enregistrons en tant que variable $pageTemplate. À la ligne 11, nous créons le conteneur de variables ViewModel, et à la ligne 14, nous définissons explicitement le nom de la vue pour le rendu.

Pour voir le système en action, ajoutons quelques pages d'affichage "statiques" : la page d'aide (help.phtml) et la page introduction (intro.phtml). Créez le sous-dossier static sous le répertoire view/application/index du module Application et placez la vue help.phtml :

<h1>Aide</h1>

<p>
    Voir l'aide <a href="<?= $this->url('static', 
	   ['page'=>'/chapter1/intro']); ?>">introduction</a> here.
</p>

Créez ensuite le sous-dossier chapter1 dans le répertoire static et placez le fichier chapter1/intro.phtml suivant:

<h1>Introduction</h1>

<p>
    Écrivez l'introduction de vos pages d'aide ici.
</p>

Au final, vous devriez avoir la structure de dossiers suivante (voir figure 5.10) :

Figure 5.10. Pages statiques Figure 5.10. Pages statiques

Éventuellement, ouvrez l'URL suivante dans votre navigateur : http://localhost/help. La page d'aide devrait apparaître (voir la figure 5.11 en exemple). Si vous tapez l'URL http://localhost/chapter1/intro dans votre navigateur, vous devriez voir la page Introduction (figure 5.12).

Figure 5.11. Page d'aide Figure 5.11. Page d'aide

Figure 5.12. Page Introduction Figure 5.12. Page Introduction

Vous pouvez créer des pages statiques en ajoutant simplement les fichiers phtml dans le répertoire static et ils seront automatiquement disponibles pour les utilisateurs du site.

Si vous êtes bloqué, vous pouvez trouver cet exemple au complet dans l'application Hello World.


Top