Close search
Hoa

Hack book de Hoa\Router

Un programme doit gérer beaucoup de requêtes, et une tâche essentielle est de savoir les router, c'est à dire de savoir où les acheminer. C'est le rôle qui incombe à la bibliothèque Hoa\Router.

Table des matières

  1. Introduction
  2. Écrire des règles
    1. Router
    2. Motifs et variables
    3. Dérouter
    4. Informations sur les requêtes
  3. Routeur HTTP
    1. Sous-domaines
    2. Fragments
    3. Ports
  4. Routeur CLI
  5. Continuer avec un dispatcheur
  6. Conclusion

Introduction

Un routeur a une tâche assez simple : il reçoit une requête et il doit trouver où l'acheminer. Pour cela, il dispose d'une série de règles. Il cherche quelle règle parmi toutes celles dont il dispose correspond à la requête. Si une correspondance entre une requête et une règle existe, alors des données seront extraites de cette requête. Ces données peuvent être utilisées pour acheminer la requête quelque part. C'est le rôle de Hoa\Dispatcher, qui fonctionne de paire avec Hoa\Router.

Une règle doit être vue comme une succession de filtres. Si tous les filtres laissent passer la requête, alors la règle sera retenue pour acheminer la requête. Une requête est traitée de la façon suivante :

Tout d'abord, une règle a une visibilité qui contrôle la provenance de la requête. Il y a deux visibilités possibles : publique, qui est validée par les requêtes provenant de l'intérieur du programme comme de l'extérieur, et privée, qui n'est validée que par les requêtes provenant du programme lui-même. Par exemple, si la requête est extérieure au programme (typiquement, un client envoie une requête sur un serveur) et que la règle a une visibilité privée, elle ne sera pas retenue. En revanche, si une requête est interne au programme, une règle publique ou privée pourra être retenue. Ensuite, une règle définit des méthodes qui vérifient le type de la requête. Par exemple, dans le cas d'une requête HTTP, nous pouvons avoir la méthode GET : toutes les règles ayant au moins la méthode GET peuvent être retenues. Enfin, une règle impose un motif sous la forme d'une expression régulière (basée sur les PCRE). La requête doit correspondre à ce motif pour qu'elle soit retenue. Ce motif permet aussi d'extraire des données de la requête, ce qui pourra aider à son acheminement. Notons par ailleurs que toutes les règles portent un identifiant unique.

Écrire des règles

Nous avons vu qu'une règle est composée d'une visibilité, de méthodes et d'un motif. Mais nous savons également que lorsqu'une règle a été choisie, son motif sera utilisé pour extraire des données de la requête, qui seront ensuite placées dans des variables. Une règle est donc également composée de variables. Nous avons au total quatre éléments.

Nous trouvons deux méthodes pour ajouter des règles sur un routeur : Hoa\Router\Router::addRule pour ajouter une règle publique et Hoa\Router\Router::addPrivateRule pour ajouter une règle privée. Les deux méthodes ont la même en-tête :

Les deux derniers paramètres sont optionnels. Nous verrons par la suite que le callable est en fait une variable, ce qui réduit notre liste aux quatre éléments énoncés précédemment.

Prenons un exemple avec le routeur HTTP représenté par la classe Hoa\Router\Http :

$router = new Hoa\Router\Http();
$router->addRule('h', ['get'],         '/hello')
       ->addRule('l', ['get', 'post'], '/login');

Nous avons déclarés deux règles : h et l. La règle h n'est accessible qu'à travers la méthode HTTP GET et seule la requête (l'URI) /hello correspond. La règle l n'est accessible qu'à travers les méthodes HTTP GET et POST et seule la requête /login correspond. Toutes les deux sont des règles publiques.

Il existe un raccourci pour ajouter plus rapidement et plus facilement des règles. Nous pouvons ainsi écrire :

$router->get('h',      '/hello')
       ->get_post('l', '/login');

La liste des méthodes peut être concaténées par le symbole « _ », puis utilisée comme nom de méthode sur le routeur. L'ordre des méthodes n'a toujours pas d'importance. Si nous voulons représenter toutes les méthodes, nous pourrons utiliser any :

$router->any(…);

Et enfin, pour représenter une règle privée, le nom devra commencer par le symbole « _ ». Ainsi, ces deux déclarations sont strictement équivalentes :

$router->addPrivateRule('f', ['get', 'post'], '/foobar');
$router->_get_post('f', '/foobar');

Notons que nous pouvons supprimer à tout moment une règle avec la méthode Hoa\Router\Router::removeRule à laquelle nous passons l'identifiant d'une règle. Nous sommes également capable de vérifier qu'une règle existe avec la méthode Hoa\Router\Router::ruleExists et un identifiant de règle.

Router

Maintenant que nous avons des règles, voyons à laquelle correspond une requête. Pour cela, nous allons utiliser la méthode Hoa\Router\Router::route. L'en-tête de cette méthode dépend du routeur. Nous allons nous concentrer sur le router HTTP pour illustrer le concept, soit la méthode Hoa\Router\Http::route. Le premier argument est l'URI, soit notre requête (ce paramètre est optionnel mais nous le verrons plus tard). Nous allons chercher la règle associée à l'URI /hello. Si aucune exception Hoa\Router\Exception\NotFound n'est levée, alors nous pouvons appeler la méthode Hoa\Router\Router::getTheRule pour obtenir les informations sur la règle choisie ; voyons plutôt :

$router->route('/hello');
print_r($router->getTheRule());

/**
 * Will output:
 *     Array
 *     (
 *         [0] => 0
 *         [1] => h
 *         [2] => Array
 *             (
 *                 [0] => get
 *             )
 *
 *         [3] => /hello
 *         [4] => 
 *         [5] => 
 *         [6] => Array
 *             (
 *                 [_uri] => hello
 *                 [_method] => get
 *                 [_domain] => 
 *                 [_subdomain] => 
 *                 [_call] => 
 *                 [_able] => 
 *                 [_request] => Array
 *                     (
 *                     )
 *
 *             )
 *
 *     )
 */

Les indices du tableau sont donnés par les constantes sur Hoa\Router\Router suivantes :

Ainsi, si nous voulons toutes les variables de la règle choisie, nous écrirons $theRule[$router::RULE_VARIABLES]. C'est aussi simple que ça.

L'exception Hoa\Router\Exception\NotFound signifie que la requête ne correspond à aucune règle. Par exemple :

try {
    $router->route('/foobar');
} catch (Hoa\Router\Exception\NotFound $e) {
    echo $e->getMessage();
}

/**
 * Will output:
 *     Cannot found an appropriated rule to route foobar.
 */

Elle peut être levée à différentes étapes de la méthode Hoa\Router\Router::route, avec des messages différents.

Motifs et variables

Jusqu'à maintenant, les motifs utilisés dans nos exemples sont constants : il n'y a pas de plages de caractères non-définis, pas de captures etc. Les motifs sont écrits avec des expressions régulières de type PCRE, ce qui nous permet de définir des règles avec des parties partiellement définies. Par exemple :

$router->get('h', '/hello_(?<who>\w+)');

Cela signifie que les requêtes correspondantes à la règle h sont de la forme /hello_word. La valeur de word sera placée dans la variable who. Voyons plutôt :

$router->route('/hello_gordon');
$theRule = $router->getTheRule();
print_r($theRule[$router::RULE_VARIABLES]);

/**
 * Will output:
 *     Array
 *     (
 *         [_uri] => hello_gordon
 *         [_method] => get
 *         [_domain] =>
 *         [_subdomain] =>
 *         [_call] =>
 *         [_able] =>
 *         [_request] => Array
 *             (
 *             )
 *
 *         [who] => gordon
 *     )
 */

Nous retrouvons notre variable who qui vaut gordon. Nous remarquons que le nom de certaines variables commence par le symbole « _ », comme _domain ou _request. Cela signifie que ce sont des variables déclarées par le routeur et non pas par l'utilisateur. Elles sont dites réservées. Chaque routeur a ses propres variables réservées. Notons que rien ne nous empêche d'utiliser leur nom dans une règle. Le routeur leur donne des valeurs « par défaut », c'est tout.

Nous avons extrait des données de la requête choisie. Le nombre de variables n'est pas limité. Ainsi :

$router->get('h', '/hello_(?<who>\w+)(?<format>\.[a-z]+)');

Avec /hello_gordon.html, la variable who sera égale à gordon et format sera égale à .html. Avec /hello_gordon.42, une exception Hoa\Router\Exception\NotFound sera levée car .42 n'est pas reconnu par le motif \.[a-z]+ et la requêe ne correspond à aucune autre règle.

Nous l'aurons compris, le motif est une expression régulière classique et nous utilisons les sous-masques nommés pour définir le nom des variables à extraire. Nous nous servons de son pouvoir d'expression pour filtrer (ou valider) les requêtes finement.

Quand nous précisons des variables lors d'une déclaration de règle avec Hoa\Router\Router::addRule (ou sa sœur Hoa\Router\Router::addPrivateRule), il est possible de définir des valeurs par défaut pour les variables. Par exemple, si la partie format devient optionnelle, nous voudrions que sa valeur par défaut soit .txt :

$router->get(
    'h',
    '/hello_(?<who>\w+)(?<format>\.[a-z]+)?',
    null,
    null,
    ['format' => '.txt']
);

$router->route('/hello_gordon');
$theRule = $router->getTheRule();
var_dump($theRule[$router::RULE_VARIABLES]['format']);

/**
 * Will output:
 *     string(4) ".txt"
 */

$router->route('/hello_gordon.html');
$theRule = $router->getTheRule();
var_dump($theRule[$router::RULE_VARIABLES]['format']);

/**
 * Will output:
 *     string(5) ".html"
 */

Il est important de savoir que le routeur traite les requêtes et les règles sans tenir compte de la casse, c'est à dire de la différence entre majuscule et minuscule. D'ailleurs, les données extraites des variables sont passées en minuscules selon les routeurs (ce qui est le cas de Hoa\Router\Http par exemple). En effet, dans la plupart des situations, il est souhaitable que le routeur soit insensible à la casse. En revanche, il existe certains cas rares où la casse est importante. Par exemple avec un moteur de recherche où les mots-clés de la recherche sont contenus dans l'URI.

Les PCRE définissent les internal options permettant de changer les options d'une expression à la volée et à l'intérieur même d'une expression. Par exemple : les chaînes foo/bar/baz ou FOO/bAr/BaZ correspondent à l'expression #foo/bar/baz#i car l'option globale i rend l'expression entièrement insensible à la casse. Si nous voulons que seulement bar soit sensible à la casse, nous écrirons : #foo/(?-i)bar(?i)/baz#i. Alors FOO/bar/BaZ sera valide, tout comme foo/bar/baz mais pas FOO/bAr/BaZ. Les options internes supportées par Hoa\Router sont de la forme (?options) pour activer des options et (?-options) pour désactiver des options. Dès qu'une option interne désactive la casse, les données extraites de toutes les variables ne seront pas passées en minuscule si c'était le cas avant.

Par exemple, si nous voulons que tout ce qui suit /hello_ soit sensible à la casse, nous écrirons :

$router->get('h', '/hello_(?-i)(?<who>\w+)');
$router->route('/hello_GorDON');
$theRule = $router->getTheRule();
var_dump($theRule[$router::RULE_VARIABLES]['who']);

/**
 * Will output:
 *     string(6) "GorDON"
 */

Il arrivera rarement que nous ayons besoin des options internes mais il est important de les comprendre.

Dérouter

L'opération inverse de Hoa\Router\Router::route est Hoa\Router\Router::unroute. Au minimum, il est demandé l'identifiant de la règle et une liste de variables. Cette méthode va construire une requête à partir d'une règle. Par exemple, nous aimerions produire la requête correspondante à la règle h avec comme valeur alyx pour who et rien pour le format (la valeur par défaut sera utilisée). Alors, nous écrirons :

var_dump($router->unroute('h', ['who' => 'alyx']));

/**
 * Will output:
 *     string(15) "/hello_alyx.txt"
 */

Cela implique que les requêtes, liens et autres URI de ressources peuvent être abstraits à partir d'identifiants et de variables. La syntaxe finale peut changer à tout moment sans casser l'application. Par exemple, changeons la règle h pour :

$router->get('h', '/users/(?<who>\w+)/hello(?<format>\.[a-z]+)?');
var_dump($router->unroute('h', ['who' => 'alyx']));

/**
 * Will output:
 *     string(21) "/users/alyx/hello.txt"
 */

La souplesse d'un tel mécanisme permet de réduire considérablement la maintenance des applications ou d'augmenter leur modularité.

Informations sur les requêtes

Parmi les informations que nous retrouverons sur tous les routeurs, nous avons :

Certains routeurs exposent d'autres informations, mais celles-ci sont standards.

Routeur HTTP

Passons maintenant aux spécificités des routeurs en commençant par le routeur HTTP, représenté par la classe Hoa\Router\Http.

Les méthodes supportées par le routeur sont un sous-ensemble des méthodes HTTP. Nous comptons : GET, POST, PUT, PATCH, DELETE, HEAD et OPTIONS. Les variables réservées pour la méthode Hoa\Router\Http::route sont :

Quand nous voudrons router une requête avec la méthode Hoa\Router\Http::route, nous allons travailler sur deux données : l'URI et son préfixe. L'URI est à comprendre au sens HTTP, c'est le chemin vers une ressource. Par exemple, considérons la requête HTTP suivante :

GET /Foo/Bar.html

Ici, l'URI est /Foo/Bar.html (et la méthode est GET). Le nom de domaine n'est jamais considéré, tout comme le port. Si l'URI est manquante, la méthode statique Hoa\Router\Http::getURI sera appelée.

Le préfixe de l'URI permet de préciser quelle partie au début de l'URI ne devra pas être considérée durant l'analyse. Imaginons que votre application soit accessible depuis l'URI /Forum/ ; une URI peut alors être : /Forum/Help/Newpost.html. Le routeur n'est intéressé que par la partie /Help/Newpost.html. Dans ce cas, le préfixe est Forum (les slashes avant et après n'ont pas d'importance). Ainsi :

$router->route('/Forum/Help/Newpost.html', 'Forum');

Toutefois, nous pouvons définir un préfixe pour toutes les requêtes, avec la méthode Hoa\Router\Http::setPrefix :

$router->setPrefix('Forum');
$router->route('/Forum/Help/Newpost.html');

Pour obtenir le préfixe, nous pouvons utiliser la méthode Hoa\Router\Http::getPrefix. Notons qu'avec la plupart des serveurs HTTP, Hoa\Router\Http sait détecter automatiquement le préfixe, vous n'aurez donc pas à vous soucier de cette problématique.

Nous avons également les méthodes Hoa\Router\Http::getPort pour obtenir le port et Hoa\Router\Http::isSecure pour savoir si la connexion est sécurisée ou pas.

Sous-domaines

La classe Hoa\Router\Http sait également router les sous-domaines. Commençons par les méthodes auxquelles nous avons accès :

Si nous accédons à l'hôte (au serveur) à travers une IP, les méthodes Hoa\Router\Http::getDomain et Hoa\Router\Http::getStrictDomain nous retourneront cette IP (sans le port, encore une fois). Prenons un exemple avec le domaine sub2.sub1.domain.tld :

var_dump(
    $router->getDomain(),
    $router->getStrictDomain(),
    $router->getSubdomain()
);

/**
 * Will output:
 *     string(20) "sub2.sub1.domain.tld"
 *     string(10) "domain.tld"
 *     string(9) "sub2.sub1"
 */

À l'instar des préfixes pour les URI, Hoa\Router\Http ajoute la notion de suffixe sur les sous-domaines, c'est à dire une partie à ne pas considérer durant l'analyse, mais cette fois, c'est la fin. Imaginons que votre application soit accessible depuis le domaine app.domain.tld et que nous aimerions que le routeur reconnaisse les sous-domaines user.app.domain.tld. Dans ce cas, le suffixe est app. Nous utiliserons la méthode Hoa\Router\Http::setSubdomainSuffix pour définir ce suffixe. La méthode Hoa\Router\Http::getSubdomain retournera par défaut tous les sous-domaines, suffixe inclu. Nous devons lui donner false en seul argument pour ne pas avoir le suffixe. Prenons un exemple avec le domaine gordon.app.domain.tld :

$router->setSubdomainSuffix('app');
var_dump(
    $router->getSubdomain(),
    $router->getSubdomain(false)
);

/**
 * Will output:
 *     string(10) "gordon.app"
 *     string(6) "gordon"
 */

Bien. Maintenant voyons comment dire à une règle de travailler sur les sous-domaines. Une règle est en réalité constituée de deux expressions régulières, concaténées par le symbole @ (arobase), c'est à dire [subdomain@]URI, avec la première partie qui est optionnelle. Si nous voulons reconnaître les sous-domaines de la forme user.domain.tld et les URI de la forme /Project/project.html, nous écrirons la règle p suivante (accessible avec la méthode GET uniquement) :

$router->get('p', '(?<user>.*)@/Project/(?<project>[^\.]+)\.html');

Si nous essayons de router la requête gordon.domain.tld/Project/Space-biker.html, nous obtiendrons ceci :

$router->route();
print_r($router->getTheRule());

/**
 * Will output:
 *     Array
 *     (
 *         [0] => 0
 *         [1] => p
 *         [2] => Array
 *             (
 *                 [0] => get
 *             )
 *
 *         [3] => (?<user>.*)@/Project/(?<project>[^\.]+)\.html
 *         [4] => 
 *         [5] => 
 *         [6] => Array
 *             (
 *                 [_uri] =>  gordon.domain.tld/Project/Space-biker.html
 *                 [_method] => get
 *                 [_domain] => gordon.domain.tld
 *                 [_subdomain] => gordon
 *                 [_call] => 
 *                 [_able] => 
 *                 [_request] => Array
 *                     (
 *                     )
 *
 *                 [project] => space-biker
 *                 [user] => gordon
 *             )
 *
 *     )
 */

Nous voyons bien nos deux variables : user et project respectivement définies à gordon et space-biker ! Nous retrouvons aussi le sous-domaine dans la variable réservée _subdomain, comme nous retrouvons également le domaine dans la variable réservée _domain.

Maintenant passons à l'opération inverse : dérouter. Nous utilisons la règle p et nous voulons construire la requête gordon.domain.tld/Project/Space-biker.html. Il n'y aura aucune différence avec ce que nous avons vu précédemment :

var_dump(
    $router->unroute(
        'p',
        [
            'user'    => 'gordon',
            'project' => 'Space-biker'
        ]
    )
);

/**
 * Will output:
 *     string(49) "http://gordon.domain.tld/Project/Space-biker.html"
 */

La méthode Hoa\Router\Http::unroute a deux variables réservées. Nous allons nous intéresser à la première : _subdomain. Elle permet de définir la valeur du sous-domaine, elle écrasera complètement le sous-domaine mais le suffixe sera tout de même ajouté. Ainsi :

var_dump(
    $router->unroute(
        'p',
        [
            'project'    => 'Space-biker',
            '_subdomain' => 'my-subdomain'
        ]
    )
);

/**
 * Will output:
 *     string(55) "http://my-subdomain.domain.tld/Project/Space-biker.html"
 */

Nous voyons que le sous-domaine est bien forcé à une valeur précise. La variable réservée _subdomain peut avoir comme valeur trois mots-clés, chacun étant associé à une opération sur les sous-domaines :

Prenons des exemples, ce sera plus simple. Nous sommes sur le domaine sub3.sub2.sub1.domain.tld :

$router->get('s', '(?<three>[^\.]+)\.(?<two>[^\.]+)\.(?<one>.+)@');
var_dump(
    // Normal.
    $router->unroute('s', ['three' => 'foo', 'two' => 'bar', 'one' => 'baz']),

    // Force.
    $router->unroute('s', ['_subdomain' => 'my-subdomain']),

    // Current subdomain.
    $router->unroute('s', ['_subdomain' => '__self__']),

    // No subdomain.
    $router->unroute('s', ['_subdomain' => '__root__']),

    // Shift only sub3.
    $router->unroute('s', ['_subdomain' => '__shift__']),

    // Shift two sub-domains.
    $router->unroute('s', ['_subdomain' => '__shift__ * 2'])
);

/**
 * Will output:
 *     string(29) "http://foo.bar.baz.domain.tld"
 *     string(30) "http://my-subdomain.domain.tld"
 *     string(32) "http://sub3.sub2.sub1.domain.tld"
 *     string(17) "http://domain.tld"
 *     string(27) "http://sub2.sub1.domain.tld"
 *     string(22) "http://sub1.domain.tld"
 */

Notons que le symbole @ est présent à la fin de la règle. Ce serait une erreur de l'oublier, la règle s'appliquerait sur l'URI et non pas sur les sous-domaines.

Ces trois-mots clés nous permettent de faire face aux situations les plus courantes avec les sous-domaines. En effet, il arrive fréquemment de vouloir remonter d'un sous-domaine, ou de retourner à la racine directement, tout en conservant l'abstraction offerte par le routeur.

Fragments

Nous avons parlé de deux variables réservées pour la méthode Hoa\Router\Http::unroute. Nous avons évoqué _subdomain et c'est maintenant le tour de _fragment. Cette variable réservée permet de définir le fragment d'une URI, c'est à dire la partie après le symbole # (dièse). Par exemple dans /Project/Space-biker.html#Introduction, le fragment est Introduction. Ainsi :

var_dump(
    $router->unroute(
        'p',
        [
            'user'      => 'gordon',
            'project'   => 'Space-biker',
            '_fragment' => 'Introduction'
        ]
    )
);

/**
 * Will output:
 *     string(62) "http://gordon.domain.tld/Project/Space-biker.html#Introduction"
 */

Ports

Les ports HTTP par défaut sont 80 pour une connexion non-cryptée et 443 pour une connexion cryptée (avec TLS). Pour obtenir ces valeurs de ports, nous pouvons utiliser la méthode Hoa\Router\Http::getDefaultPort. Naturellement, nous aurons le port par défaut pour une connexion non-cryptée. En revanche, si nous donnons Hoa\Router\Http::SECURE en seul argument, nous aurons le port par défaut pour une connexion cryptée. Ainsi :

var_dump($router->getDefaultPort());

/**
 * Will output:
 *     int(80)
 */

La valeur des ports par défaut se met à jour toute seule. Par exemple, si les requêtes arrivent sur une connexion non-cryptée à travers le port 8880, Hoa\Router\Http changera 80 par 8880 automatiquement. Toutefois, pour modifier les ports par défaut manuellement, nous utiliserons la méthode Hoa\Router\Http::setDefaultPort, avec en premier argument la valeur du port et en second argument l'une des deux constantes : Hoa\Router\Http::SECURE ou Hoa\Router\Http::UNSECURE, indiquant si c'est pour une connexion cryptée ou pas.

Ces numéros de port par défaut sont importants quand nous appelons la méthode Hoa\Router\Http::unroute et qu'un domaine avec un port doit être reconstitué. C'est le cas, par exemple, si nous forçons à dérouter vers une connexion cryptée, à l'aide du troisième argument de cette méthode en lui donnant Hoa\Router\Http::SECURE :

var_dump(
    $router->unroute(
        'p',
        ['user' => 'gordon', 'project' => 'Space-biker'],
        true
    )
);
$router->setDefaultPort(8443, Hoa\Router\Http::SECURE);
var_dump(
    $router->unroute(
        …
    )
);

/**
 * Will output:
 *     string(50) "https://gordon.domain.tld/Project/Space-biker.html"
 *     string(55) "https://gordon.domain.tld:8443/Project/Space-biker.html"
 */

Nous remarquons que le protocole HTTPS est utilisé. Dans le premier cas, le port n'est pas affiché car sa valeur par défaut est 443 et c'est un standard. En revanche, quand nous modifions le port par défaut pour 8443, le port est bien affiché.

Routeur CLI

Le routeur CLI permet de manipuler des requêtes dans un terminal. Il est représenté par la classe Hoa\Router\Cli.

Ce routeur ne supporte qu'une seule méthode : GET. Les variables réservées pour la méthode Hoa\Router\Cli::route sont :

La méthode Hoa\Router\Cli::route n'a qu'un seul argument : l'URI ; par exemple avec :

$ command --option value input

Dans ce contexte, l'URI est la ligne de commande, ou plus précisément, ce qui suit la commande, sans distinction ; soit --option value input. Nous pouvons nous en rendre compte en appelant la méthode statique Hoa\Router\Cli::getURI. Il n'y pas de notion de préfixe comme pour Hoa\Router\Http.

Prenons un exemple très courant qui consiste à avoir une ligne de commande la forme command group:subcommand options. Nous écrirons la règle g suivante dans le fichier Router.php :

$router = new Hoa\Router\Cli();
$router->get(
    'g',
    '(?<group>\w+):(?<subcommand>\w+)(?<_tail>.*?)'
);

$router->route();
$theRule = $router->getTheRule();
print_r($theRule[$router::RULE_VARIABLES]);

Nous pouvons exécuter le fichier de la façon suivante :

$ php Router.php foo:bar baz
Array
(
    [group] => foo
    [subcommand] => bar
    [_call] => 
    [_able] => 
    [_tail] =>  baz
)

La variable _tail a une signification particulière. Il faut savoir que nous nous en servons pour capturer les options et les entrées d'une ligne de commande afin de les analyser avec Hoa\Console par la suite.

Nous pouvons avoir envie que group soit optionnel (avec default comme valeur par défaut), tout comme subcommand (avec la même valeur par défaut). Dans ce cas, la règle deviendrait :

$router->get(
    'g',
    '(?<group>\w+)?(:(?<subcommand>\w+))?(?<_tail>.*?)',
    null,
    null,
    [
        'group'      => 'default',
        'subcommand' => 'default'
    ]
);

Ainsi, nous pourrions avoir group, group:subcommand ou :subcommand. Testons avec subcommand absent dans un premier temps, puis avec group absent dans un second temps :

$ php Router.php foo baz
Array
(
    [group] => foo
    [subcommand] => default
    [_call] =>
    [_able] =>
    [_tail] =>  baz
)
$ php Router.php :baz baz
Array
(
    [group] => default
    [subcommand] => bar
    [_call] =>
    [_able] =>
    [_tail] =>  baz
)

Rien de plus simple avec Hoa\Router\Cli. Nous pouvons toujours regarder le script hoa (dans hoa://Library/Cli/Bin/Hoa.php) pour avoir un exemple concret.

Continuer avec un dispatcheur

Lorsque le routeur reçoit une requête et qu'il trouve une règle qui la reconnaît, il pourra en extraire des données. C'est ce que fait Hoa\Router\Router::route. Nous obtenons les informations de la règle avec Hoa\Router\Router::getTheRule. Nous remarquons que tous les routeurs ont les variables réservées _call et _able : elles permettent de définir un callable. Ces variables peuvent être alors réutilisées pour dispatcher la requête. C'est exactement le rôle de Hoa\Dispatcher et c'est la suite logique de Hoa\Router.

Conclusion

La bibliothèque Hoa\Router offre une interface et un fonctionnement commun à des routeurs. Un routeur est capable de reconnaître une requête à partir de plusieurs règles et d'en extraire des données. Actuellement, deux routeurs sont proposés : Hoa\Router\Http et Hoa\Router\Cli.

Une erreur ou une suggestion sur la documentation ? Les contributions sont ouvertes !

Comments

menu