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.

L’élève attentif aura remarqué que notre framework code en dur la manière dont le « code » spécifique (celui des templates) est lancé. Pour des pages simples comme celles que nous avons créées pour le moment, pas de problèmes, mais si vous voulez ajouter plus de logique, vous serez forcés de mettre la logique dans un template lui même, ce qui n’est probablement pas une bonne idée, en particulier si vous avez toujours en tête l’idée de séparation des responsabilités.

Séparons le code de template de la logique en ajoutant une nouvelle couche : le controleur. La mission du controleur est de générer une réponse à partir des informations fournies par la requête.

Changez la partie de rendu des templates du framework de la manière suivante :


attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func('render_template', $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}


Comme le rendu est désormais réalisé par une fonction externe (render_template() ici), nous devons lui fournir les attributs extraits de l’URL. Nous aurions pu les fournir comme argument à render_template, mais à la place, utilisons une autre fonctionnalité de la classe Request, les attributs. Les attributs de requête vous permettent de lier des informations additionnelles sur la requête qui ne sont pas directement liées aux données de la requête HTTP.

Vous pouvez maintenant créer la fonction render_template(), un controleur générique qui effectue le rendu d’un template où il n’y a pas de logique spécifique. Pour garder le même modèle qu’avant, les attributs de requête sont extraits avant le rendu du template :


function render_template($request)
{
extract($request->attributes->all(), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);

return new Response(ob_get_clean());
}

Comme render_template est utilisée en argument à la fonction PHP call_user_func(), nous pouvons la remplacer par n’importe quel callback PHP valide. Cela nous permet d’utiliser une fonction, une fonction anonyme ou la méthode d’une classe comme controleur… à vous de choisir.

Comme convention, pour chaque route, le controleur associé est configuré via l’attribut de route _controller :


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

try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}

Une route peut maintenant être associée à un controleur et, bien sûr, à l’intérieur d’un controleur, vous pouvez toujours appeler render_template pour afficher un template :


$routes->add('hello', new Routing\Route('/hello/{name}', array(
'name' => 'World',
'_controller' => function ($request) {
return render_template($request);
}
)));

C’est plutôt flexible, car vous pouvez changer l’objet Response par la suite et on peut même fournir des arguments supplémentaires au template :


$routes->add('hello', new Routing\Route('/hello/{name}', array(
'name' => 'World',
'_controller' => function ($request) {
// $foo will be available in the template
$request->attributes->set('foo', 'bar');

$response = render_template($request);

// change some header
$response->headers->set('Content-Type', 'text/plain');

return $response;
}
)));

Voici la version mise à jour et améliorée de notre framework :


attributes->all(), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);

return new Response(ob_get_clean());
}

$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';

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

try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}

$response->send();

Pour fêter la naissance de notre nouveau framework, créons une toute nouvelle application qui a besoin d’un peu de logique simple. Notre application a une page qui dit si une année donnée est bissextile ou non. Lorsqu’on appelle /is_leap_year, on a la réponse pour l’année en cours, mais on peut également spécifier l’année via /is_leap_year/2009« . Etant générique, le framework n’a pas besoin d’être modifié, il suffit de créer un nouveau fichier app.php :


add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => function ($request) {
if (is_leap_year($request->attributes->get('year'))) {
return new Response('Yep, this is a leap year!');
}

return new Response('Nope, this is not a leap year.');
}
)));

return $routes;

La fonction is_leap_year renvoit true lorsque l’année est bissextile, et false sinon. Si l’année vaut null, c’est l’année en cours qui est testée. Le controleur est simple : il récupère l’année à partir des attributs de la requête, et fournit cette information à la fonction is_leap_year(), et en fonction de la valeur de retour un objet Response est créé.

Comme toujours, vous pouvez décider de vous arrêter là et utiliser le framework tel quel; c’est probablement tout ce dont vous avez besoin pour créer des sites web simple comme ces sites sympa en une page, et, espérons-le, pour quelques autres.

Répondre