Permalink
Browse files

Add Routes Middlewares

  • Loading branch information...
DrBenton committed Mar 10, 2012
1 parent 8d30526 commit 6445b9e9e8be84cdb3181b0c92f62800e3159185
Showing with 203 additions and 0 deletions.
  1. +57 −0 doc/usage.rst
  2. +13 −0 src/Silex/Application.php
  3. +16 −0 src/Silex/Controller.php
  4. +117 −0 tests/Silex/Tests/ApplicationTest.php
View
@@ -352,6 +352,63 @@ object that is returned by the routing methods::
It only makes sense to name routes if you use providers that make use
of the ``RouteCollection``.
Routes middlewares
~~~~~~~~~~~~~~~~~~
You can define one or more Routes Middlewares, and link them to your routes.
Routes Middlewares are just "PHP callables" (i.e. a Closure or a
"ClassName::methodName" string, like others Silex callbacks), which will
be triggered when their route is matched.
Middlewares are fired just before the route callback,
but after Application ``before`` filters, which have precedence
- see next section about these ``before`` filters.
This mechanism can be used for a lot of use case - for example, a
"anonymous/logged user" simple control::
$mustBeAnonymous = function (Request $request) use ($app) {
if ($app['session']->has('userId')) {
return $app->redirect('/user/logout');
}
};
$mustBeLogged = function (Request $request) use ($app) {
if (!$app['session']->has('userId')) {
return $app->redirect('/user/login');
}
};
$app->get('/user/subscribe', function () {
...
})
->middleware($mustBeAnonymous);
$app->get('/user/login', function () {
...
})
->middleware($mustBeAnonymous);
$app->get('/user/my-profile', function () {
...
})
->middleware($mustBeLogged);
You can call the ``middleware`` function several times for a single route.
The middlewares will be triggered in the order you added them to the route.
For convenience, the routes middlewares functions are triggered with the current
Request as their only argument.
If any of the routes middlewares returns a Symfony Http Response, this response
will short-circuit the whole rendering : the next middlewares won't run, neither
the route callback.
As in route callbacks, you can redirect to another page by from a route middleware
by returning a redirect response, which you can create by calling the
Application ``redirect`` method.
A route Middleware can return a Symfony Http Response or null.
A RuntimeException will be thrown if anything else is returned.
Before and after filters
------------------------
View
@@ -111,6 +111,18 @@ public function __construct()
return new RedirectableUrlMatcher($app['routes'], $app['request_context']);
});
$this['route_middlewares_trigger'] = $this->protect(function (KernelEvent $event) use ($app) {
foreach ($event->getRequest()->attributes->get('_middlewares', array()) as $callback) {
$ret = call_user_func($callback, $event->getRequest());
if ($ret instanceof Response) {
$event->setResponse($ret);
return;
} elseif (null !== $ret) {
throw new \RuntimeException('Middleware for route "'.$event->getRequest()->attributes->get('_route').'" returned an invalid response value. Must return null or an instance of Response.');
}
}
});
$this['request.default_locale'] = 'en';
$this['request'] = function () {
@@ -409,6 +421,7 @@ public function onKernelRequest(KernelEvent $event)
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$this->beforeDispatched = true;
$this['dispatcher']->dispatch(SilexEvents::BEFORE, $event);
$this['route_middlewares_trigger']($event);
}
}
View
@@ -154,6 +154,22 @@ public function requireHttps()
return $this;
}
/**
* Sets a callback to handle before triggering the route callback.
* (a.k.a. "Route Middleware")
*
* @param mixed $callback A PHP callback to be triggered when the Route is matched, just before the route callback
* @return Controller $this The current Controller instance
*/
public function middleware($callback)
{
$middlewareCallbacks = $this->route->getDefault('_middlewares');
$middlewareCallbacks[] = $callback;
$this->route->setDefault('_middlewares', $middlewareCallbacks);
return $this;
}
/**
* Freezes the controller.
*
@@ -15,6 +15,9 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Application test cases.
@@ -166,6 +169,120 @@ public function testHttpSpec()
$this->assertEquals('text/html; charset=ISO-8859-1', $response->headers->get('Content-Type'));
}
public function testRoutesMiddlewares()
{
$app = new Application();
$test = $this;
$middlewareTarget = array();
$middleware1 = function (Request $request) use (&$middlewareTarget, $test) {
$test->assertEquals('/reached', $request->getRequestUri());
$middlewareTarget[] = 'middleware1_triggered';
};
$middleware2 = function (Request $request) use (&$middlewareTarget, $test) {
$test->assertEquals('/reached', $request->getRequestUri());
$middlewareTarget[] = 'middleware2_triggered';
};
$middleware3 = function (Request $request) use (&$middlewareTarget, $test) {
throw new \Exception('This middleware shouldn\'t run!');
};
$app->get('/reached', function () use (&$middlewareTarget) {
$middlewareTarget[] = 'route_triggered';
return 'hello';
})
->middleware($middleware1)
->middleware($middleware2);
$app->get('/never-reached', function () use (&$middlewareTarget) {
throw new \Exception('This route shouldn\'t run!');
})
->middleware($middleware3);
$result = $app->handle(Request::create('/reached'));
$this->assertSame(array('middleware1_triggered', 'middleware2_triggered', 'route_triggered'), $middlewareTarget);
$this->assertEquals('hello', $result->getContent());
}
public function testRoutesMiddlewaresWithResponseObject()
{
$app = new Application();
$app->get('/foo', function () {
throw new \Exception('This route shouldn\'t run!');
})
->middleware(function () {
return new Response('foo');
});
$request = Request::create('/foo');
$result = $app->handle($request);
$this->assertEquals('foo', $result->getContent());
}
public function testRoutesMiddlewaresWithRedirectResponseObject()
{
$app = new Application();
$app->get('/foo', function () {
throw new \Exception('This route shouldn\'t run!');
})
->middleware(function () use ($app) {
return $app->redirect('/bar');
});
$request = Request::create('/foo');
$result = $app->handle($request);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);
$this->assertEquals('/bar', $result->getTargetUrl());
}
public function testRoutesMiddlewaresTriggeredAfterSilexBeforeFilters()
{
$app = new Application();
$middlewareTarget = array();
$middleware = function (Request $request) use (&$middlewareTarget) {
$middlewareTarget[] = 'middleware_triggered';
};
$app->get('/foo', function () use (&$middlewareTarget) {
$middlewareTarget[] = 'route_triggered';
})
->middleware($middleware);
$app->before(function () use (&$middlewareTarget) {
$middlewareTarget[] = 'before_triggered';
});
$app->handle(Request::create('/foo'));
$this->assertSame(array('before_triggered', 'middleware_triggered', 'route_triggered'), $middlewareTarget);
}
/**
* @expectedException RuntimeException
*/
public function testNonResponseAndNonNullReturnFromRouteMiddlewareShouldThrowRuntimeException()
{
$app = new Application();
$middleware = function (Request $request) {
return 'string return';
};
$app->get('/', function () {
return 'hello';
})
->middleware($middleware);
$app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false);
}
/**
* @expectedException RuntimeException
*/

0 comments on commit 6445b9e

Please sign in to comment.