Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

Cet article est la traduction d’un article original de Fabien Potencier, à l’origine de Symfony2, disponible ici.

Avant de commencer avec le sujet d’aujourd’hui, commençons par refactorer un peu notre framework actuel pour rendre les templates encore plus lisibles :

// example.com/web/front.php

require_once __DIR__.'/../src/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$map = array(
'/hello' => 'hello',
'/bye' => 'bye',
);

$path = $request->getPathInfo();
if (isset($map[$path])) {
ob_start();
extract($request->query->all(), EXTR_SKIP);
include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
$response = new Response(ob_get_clean());
} else {
$response = new Response('Not Found', 404);
}

$response->send();


Comme nous extrayons maintenant les paramètres de la requête, simplifions le template hello.php de la manière suivante :


Hello

Maintenant, nous sommes prêts à ajouter de nouvelles fonctionnalités.

Un aspect très important de n’importe quel site web est la forme de ses URLs. Grâce à la table des URL, nous avons découplé les URL du code qui génère la réponse associée, mais cela n’est pas suffisant. Par exemple, nous pouvons vouloir des chemins dynamiques qui permettent d’embarquer directement des données dans l’URL, au lieu de nous baser sur la chaine de la requête :


# Avant
/hello?name=Fabien

# Après
/hello/Fabien

Pour supporter cette fonctionnalité, nous allons utiliser le composant Routing de Symfony2. Comme toujours, ajoutez une dépendance dans le fichier composer.json et lancez la commande php composer.phar update pour l’installer:


{
"require": {
"symfony/class-loader": "2.1.*",
"symfony/http-foundation": "2.1.*",
"symfony/routing": "2.1.*"
}
}

A partir de maintenant, nous allons utiliser l’autoloader de Composer au lieu de notre autoload.php. Supprimez le fichier autoload.php et remplacez sa référence dans front.php :


Au lieu d'utiliser un tableau associatif pour stocker la liste des URLs, le composant Routing utilise une instance de RouteCollection :


use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();

Ajoutons une route qui décrive l'URL /hello/QUELQUECHOSE et ajoutons-en également une simple pour l'URL /bye :


use Symfony\Component\Routing\Route;

$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Route('/bye'));

Chaque entrée dans la liste est définie par un nom (hello) et une instance de Route, définie par un modèle de route (``/hello/{name}``) et un tableau de valeurs par défaut pour les attributs de la route (array('name' => 'World')).

Lisez la documentation officielle du composant Routing pour en apprendre plus sur ses nombreuses fonctionnalités tel que la génération d'URL, les caractéristiques des attributs, le forçage de la méthode HTTP, le chargement via des fichiers de configuration YAML ou XML, la génération de règles de réécriture PHP ou Apache pour améliorer les performances, et bien plus.

A partir des informations présentes dans l'instance de RouteCollection, une instance de l'objet UrlMatcher permet d'associer les URL :

use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;

$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);

$attributes = $matcher->match($request->getPathInfo());

La méthode match() prend en paramètres un chemin de requête et renvoie un tableau d'attributs. Vous pouvez remarquer que la route associée est automatiquement stockée dans l'attribut spécial _route :


print_r($matcher->match('/bye'));
array (
'_route' => 'bye',
);

print_r($matcher->match('/hello/Fabien'));
array (
'name' => 'Fabien',
'_route' => 'hello',
);

print_r($matcher->match('/hello'));
array (
'name' => 'World',
'_route' => 'hello',
);

Même si n'avons pas strictement besoin du contexte de la requête dans nos exemples, c'est utilisé dans la pratique, entre autres pour forcer la méthode HTTP à utiliser.

L'UrlMatcher lance une exception lorsqu'aucune des routes ne correspond :


$matcher->match('/not-found');

// Lance une exception Symfony\Component\Routing\Exception\ResourceNotFoundException

En ayant ceci en tête, écrivons une nouvelle version de notre framework :

fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);

try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);

$response = new Response(ob_get_clean());
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}

$response->send();

Il y a quelques nouvelles choses dans le code :

  • Les noms de routes sont utilisés dans les noms de template;
  • Les erreurs 500 sont maintenant gérées correctement;
  • La configuration des Routes a été déplacée dans son propre fichier :

  • add('hello', new Routing\Route('/hello/{name}', array('name' => 'World')));
    $routes->add('bye', new Routing\Route('/bye'));

    return $routes;

  • Les attributs de requête sont extraits pour garder les templates simples :
  • // example.com/src/pages/hello.php
    echo 'Hello '.htmlspecialchars($name, ENT_QUOTES, 'UTF-8');

Nous avons maintnenant une séparation claire entre la configuration (tout ce qui est spécifique à notre application se trouve dans app.php) et le framework (le code générique qui anime notre application est dans front.php).

Avec moins de 30 lignes de code, nous avons un nouveau framework, plus puissant et plus flexible que le précédent. C'est cool !

Utiliser le composant Routing a un gros avantage supplémentaire : la possibilité de générer des URLs à partir des définitions de Route. En utilisant à la fois l'association d'URL et la génération d'URL dans votre code, changer les modèles d'URLs ne devrait pas avoir d'impact. Vous voulez savoir comment utiliser le générateur ? C'est incroyablement facile :


use Symfony\Component\Routing;

$generator = new Routing\Generator\UrlGenerator($routes, $context);

echo $generator->generate('hello', array('name' => 'Fabien'));
// affiche /hello/Fabien

Le code parle de lui même; grâce au contexte, vous pouvez même générer des URLs absolues :


echo $generator->generate('hello', array('name' => 'Fabien'), true);
// affiche quelque chose comme http://example.com/somewhere/hello/Fabien

Vous vous inquiétez pour les performance ? Grâce aux définitions de routes, vous pouvez créer une classe de résolution d'URL fortement optimisée qui permet de remplacer la classe par défaut UrlMatcher :


$dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);

echo $dumper->dump();

Vous voulez encore plus de performances ? Générez un ensemble de règles de réécriture Apache à partir de vos routes :


$dumper = new Routing\Matcher\Dumper\ApacheMatcherDumper($routes);

echo $dumper->dump();

Répondre