Ecrire une route personnalisée avec le Zend Framework

Voici un petit tutoriel très simple pour créer une route personnalisée à votre application Zend Framework.

Donc avons toujours notre cher bootstrap dans lequel nous allons ajouter la méthode Bootstrap::_initRouter() pour ajouter notre route.

Qu'allons nous faire ?

Si nous voulons par exemple faire ce qu'il y a de plus classique : de la réécriture d'URL avec le Zend Framework, vous ne pouvez évidemment pas passer par la configuration Apache car le routeur par défaut du Zend Framework analyse l'URI et la segmente en 3 parties (module/controller/action) et si l'URI est plus longue, les autres segments seront considérés comme paramètres.

En savoir plus dans la section dédiée.

Ici, nous faire quelque chose de simple, un controller et une son action par défaut (indexAction()) pour gérer des URI suivant un certain pattern qui sera, par exemple :

http://domain/article/2009-26-mon-article-de-ouf.html
http://domain/article/2009-2-mon-2nd-article.htm
// etc.

Pour cela, nous allons récupérer notre router par défaut via cette ligne :

<?php
$router = Zend_Controller_Front::getInstance()->getRouter();

Ensuite nous allons créer notre nouvelle route personnalisée qui sera l'objet d'une instance de classe Zend_Controller_Router_Route_Regex.
Le constructeur de cette classe déclare 4 paramètres dont un obligatoire :

  1. Le pattern de la route (donc son expression régulière) ;
  2. Un tableau spécifiant à quel controller/action vous allez associer la route ;
  3. Un tableau permettant de mapper les numéros de paramètre ;
  4. Le pattern permettant de générer vos URI avec l'aide de vue url().

Construisons notre nouvelle route en essayant de comprendre l'intérêt de chacun des paramètres :

<?php
$route = new Zend_Controller_Router_Route_Regex('article/(\d+)-(\d+)-([-\w]+)\.html?', array(
    'controller' => 'article',
    'action'     => 'index'
), array(
    1   => 'article_year',
    2   => 'article_id',
    3   => 'article_title_uri'
), 'article/%s-%s-%s.html');

Nous avons bel et bien notre expression régulière en premier paramètre qui va mapper nos pages vers la route que nous avons défini dans le tableau du second paramètre. Ensuite viennent les 2 autres paramètres.
Si vous êtes familier des expressions régulières, vous saurez que les paramètres sont identifiés par les sous-expressions qui sont situées dans les parenthèses de l'expression globale (ici ce sera (\d), (\d) et ([-\w])) ; et qu'on récupère via la variable $1 pour la première parenthèse, $2 pour la seconde, etc.

Le troisième paramètre va nous permettre de remplacer ces numéros de paramètres par des noms plus facilement identifiables et utilisables.
Explications : c'est comme si au lieu d'avoir une expression qui ressemblerait à cela : article/$1-$2-$3.html, nous aurions ceci : article/$article_year-$article_id-$article_title_uri.html.

Avouez que c'est déjà bien plus agréable pour traiter les paramètres... Personnellement, je trouve que c'est vraiment une bonne initiative d'avoir permis de mapper les paramètres et vous allez comprendre pourquoi juste maintenant.

Le dernier paramètre du constructeur vous permets de générer des URI via l'aide de vue Zend_Controller_Action_Helper_Url::url(). En fait, cela revient à l'équivalent du premier paramètre mais non pas en expression régulière mais de manière à être interprété par la fonction PHP sprintf().
Vous pourriez très bien écrire vos URI à la main mais imaginez que vous mettez à jour votre route, allez-vous passer derrière tous vos liens générés et changer manuellement leur pattern ?

L'aide vue Zend_Controller_Action_Helper_Url::url() nous permet de générer des URI automatiquement en lui fournissant le nom de la route (en second paramètre) et les paramètres de l'URI sous forme de tableau en premier paramètre de la méthode.

Nous sommes dans notre script de vue :

<?php
// Nous sommes dans une boucle avec la variable $article_item
// Générons une liste de liens permettant d'acceder à l'article courant
$uri = $this->url(array(
    1 => $article_item->article_year,
    2 => $article_item->article_id,
    3 => $article_item->article_title_uri
), 'article');

echo '<li><a href="' . $uri . '">' . $article_item->article_title . '</a></li>';

Ok, c'est tout pourri comme ça... C'est 10 fois mieux ainsi :

<?php
$uri = $this->url(array(
    'article_year'      => $article_item->article_year,
    'article_id'        => $article_item->article_id,
    'article_title_uri' => $article_item->article_title_uri
), 'article');

Beaucoup plus clair n'est-ce pas ? Ainsi on peut même générer le tableau de manière automatique avec Zend_Controller_Router_Route_Regex::getVariables() qui retourne le contenu du 3ème argument du constructeur.
La ça commence sérieusement à devenir intéressant...

Nous allons en profiter :

<?php
class ArticleController extends Zend_Controller_Action
{
    public function init()
    {
    }
 
    public function indexAction()
    {
        $this->view->route_variables = Zend_Controller_Front::getInstance()
            ->getRouter()
            ->getRoute('article')
            ->getVariables();
    }
}

Et dans notre script de vue :

<?php
// Nous sommes dans une boucle avec la variable $article_item
// Générons une liste de liens permettant d'acceder à l'article courant
// Nous allons associer les valeurs de l'article courant directement avec les clés de la route
$article_uri_values = array(
    $article_item->article_year,
    $article_item->article_id,
    $article_item->article_title_uri
);
$uri = $this->url(array_combine($this->route_variables, $article_uri_values), 'article');

echo '<li><a href="' . $uri . '">' . $article_item->article_title . '</a></li>';

Et encore, on pourrait même pousser le vice à automatiser cette tâche dans notre objet $article_item via une méthode Article::generateUri(). Laissez libre cours à votre imagination.

Pour en finir...

Si vous avez tout suivi, vous devriez facilement arriver à gérer et générer de manière très propre des routes aussi complexes soient-elles. Pour en finir avec notre bootstrap, voici ce qu'il devrait contenir pour ajouter la route article :

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initRouter()
    {
        // Récupère l'instance courante du router actuel si il existe sinon en créer un
        $router = Zend_Controller_Front::getInstance()->getRouter();
 
        // Défini la route 'article'
        $route = new Zend_Controller_Router_Route_Regex('article/(\d+)-(\d+)-([-\w]+)\.html?', array(
            'controller' => 'article',
            'action'     => 'index'
        ), array(
            1   => 'article_year',
            2   => 'article_id',
            3   => 'article_title_uri'
        ), 'article/%s-%s-%s.html');
 
        // Ajoute la route au router et renvoi l'objet
        $router->addRoute('article', $route);
        return $router;
    }
}

Félicitations, vous venez de comprendre en 15 min mes 2 jours de recherche sur le router du Zend Framework ! Si vous avez des soucis, n'hésitez pas à laisser un commentaire.