Surcharger une route d'un contrôleur séparé dans Silex
index.php
à une multitude de contrôleurs distincts.Tout cela est bien beau, mais peut l'être encore plus si vous organisez vos contrôleurs de manière à faire certains d'entre eux hériter d'autres. Ceci peut être particulièrement utile si vous avez plusieurs contrôleurs dont les fonctionnalités sont très similaires. Vous pouvez par exemple procéder ainsi :
<?php
namespace MyCoolApp\Controller;
// je zappe volontairement les use pour gagner du temps
abstract class AbstractController implements ControllerProviderInterface
{
public function connect(Application $app)
{
// Cette méthode est appelée par défaut par Silex lorsque vous "montez" un contrôleur dans votre application.
return $this->route($app);
}
protected function route($app, $extra_params = null)
{
$ctrl = $app['controllers_factory'];
$ctrl->get(
'/my-first-uri',
function () use ($app, $extra_params) {
// ...
}
);
$ctrl->get(
'/my-second-uri',
function () use ($app, $extra_params) {
// ...
}
);
return $ctrl;
}
}
<?php
namespace MyCoolApp\Controller;
// idem
class TrueController extends AbstractController
{
protected function route($app, $extra_params = array('param1' => 'value1', 'param2' => 'value2'))
{
// En une ligne, nous assignons à ce contrôleur toutes les routes déclarées dans son parent
// Celles-ci récupèrent du même coup des paramètres supplémentaires propres à chaque classe fille
$ctrl = parent::route($app, $extra_params);
// Nous pouvons déclarer d'autres routes ici le cas échéant...
return $ctrl;
}
}
Plutôt sympa, non ? Pourtant, il reste un souci de taille : lorsqu'il sélectionne la route qui sera utilisée pour répondre à une requête, Silex prend la première qu'il trouve. Dans ce que nous venons de mettre en place, les routes du parent sont définies avant celles de l'enfant ; ainsi, si nous faisons la chose suivante :
// Dans le parent
$ctrl->post(
'/my-post-uri',
function () use ($app) {
return new Response('Luke, je suis ton père !');
}
);
// Dans l'enfant (roooh)
$ctrl->post(
'/my-post-uri',
function () use ($app) {
return new Response('Areuh areuh');
}
);
Lorsqu'on appellera
/my-post-uri
dans le contrôleur (enfant) concerné, c'est le code du parent qui s'exécutera et la célèbre réplique de film qui s'affichera sous nos yeux ébahis.Notez que dans certains cas (et notamment sur des routes utilisant la méthode
GET
), la version de l'enfant supplantera celle du parent. Je vous avoue ne pas avoir suffisamment plongé dans le code pour m'expliquer pourquoi, mais je serai ravi de l'apprendre si vous avez la réponse.Il faudrait donc que nous ayons la possibilité de "supprimer" une route déclarée préalablement dans un contrôleur, afin de pouvoir la supplanter dans un tel cas. Hélas,
$app['controllers_factory']
, en tant qu'instance de Silex\ControllerCollection
, ne nous le permet pas. Qu'à cela ne tienne, nous allons étendre cette classe afin de le lui apprendre ! Nous nommerons la méthode idoine cancel
, afin d'éviter toute confusion avec la méthode delete
:<?php
namespace MyCoolApp\Whatever;
use Silex\ControllerCollection as BaseControllerCollection;
class ControllerCollection extends BaseControllerCollection
{
public function cancel($path, $methods = array('GET', 'POST', 'PUT', 'DELETE'))
{
$methods = array_map('strtoupper', (array)$methods);
foreach ($this->controllers as $key => $controller) {
$route = $controller->getRoute();
// La route courante nous intéresse si :
// - son path est identique à celui recherché
// - elle emploie une ou plusieurs méthodes parmi celles que nous voulons annuler pour ledit path
if (($route->getPath() == $path) && (count(array_intersect($methods, $route->getMethods())))) {
$methods_diff = array_diff($route->getMethods(), $methods);
if (! count($methods_diff)) {
// Si nous éliminons toutes les méthodes pour cette route, nous pouvons la faire disparaître totalement
unset($this->controllers[$key]);
} else {
// Sinon, on redéfinit ses méthodes avec ce qui reste
$controller->getRoute()->setMethods($methods_diff);
}
}
}
return $this; // pour pouvoir chaîner cette méthode avec d'autres
}
}
Il nous faut ensuite, dans
index.php
, indiquer à Silex d'utiliser cette classe :$app = new Silex\Application();
$app['controllers_factory'] = function () use ($app) {
return new MyCoolApp\Whatever\ControllerCollection($app['route_factory']);
};
// Faites ensuite vos $app->mount(...)
Enfin, cette méthode s'utilisera ainsi, dans la classe fille :
$ctrl->cancel('/my-post-uri', 'post');
$ctrl->post(
'/my-post-uri',
function() use ($app) {
return new Response('Areuh areuh');
}
);
Cette fois, la même requête recevra bien la réponse attendue.