Commit 0ff83c40 authored by Fabien Potencier's avatar Fabien Potencier

merged branch fabpot/events-refactoring (PR #525)

This PR was merged into the master branch.

Commits
-------

02a39976 moved the modularity section of the docs to its own section
cbc769ce fixed CS
d669a7d5 made a small refactoring for better consistency
deb42381 refactored the exception wrapper to make the code easier to understand
e54627f6 moved exception wrapper to its own class
5fefc7dd fixed PHP 5.4 warning when a response is an array, dispatch the view event when an error does not return a Response
a57f50d3 udpated docs
8772cbca moved the before middleware to always be run after the app middlewares
63e24c54 added the application as a third argument to the after middleware (to be consistent with the before one)
09a61047 moved the converter and string to response listeners to proper classes
f51a1f24 moved the middleware events to a proper event listener class
3108bfa1 moved the locale management to its own event listener
ca79d330 moved all subscribers registration into the dispatcher closure creation
056fc32f refactored Silex events (refs #503, refs #504, refs #452)
52a2fdaf removed the wrapping of the event for the exception event

Discussion
----------

[WIP] Events refactoring

This PR tries to address the issues reported on the `before()` method and more generally, the issues we have with the way we manage events in Silex.

Silex has special events (defined in SilexEvents), and they map with Symfony events. For instance, the `BEFORE` event maps to the `REQUEST` one from Symfony. The main difference is that the priority you set for a `BEFORE` event can only configure the priority compared to other `BEFORE` events; all the events for the `BEFORE` event are dispatched from a single priority on the `REQUEST` event. This behavior removes a lot of flexibility for no reasons. And the same goes for the other Silex events.

So, this PR does is to remove the specific Silex events and replace them with regular Symfony ones. This gives you more flexibility. For instance, it means that now, you can use the `before()` method to do something before the routing (priority `Application::EARLY_EVENT`) or after it (`Application::LATE_EVENT`). And if you really need to do something after the routing and before the security, that's also possible.

The biggest benefit is that with the `before()`, `after()`, `error()`, and `finish()' methods, you can pretty much change all the default Symfony behaviors. So, adding a listener directly to the dispatcher should be now pretty rare.

This PR is not finished yet as the documentation is not updated (but with the added flexibility, it's going to be much easier to document exactly what you can do and how in a consistent manner), but I want to gather feedback on the philosophy first.

---------------------------------------------------------------------------

by fabpot at 2012-11-03T07:56:18Z

I forgot to mention that this breaks BC, but only for edge cases (and this will be documented of course).

---------------------------------------------------------------------------

by yguedidi at 2012-11-03T20:00:35Z

+1

---------------------------------------------------------------------------

by igorw at 2012-11-04T04:34:56Z

I am generally 👍 for this change.

---------------------------------------------------------------------------

by fabpot at 2012-11-04T07:55:04Z

I've added two commits that fix two other issues:

* ca79d330: all subscribers are now registered during the creation of the dispatcher (the exception handler is not a special case anymore)
* 3108bfa1: to avoid any problem with the local management, it is now handled in a proper listener that extends the Symfony one.

---------------------------------------------------------------------------

by fabpot at 2012-11-04T08:20:36Z

Another commit (f51a1f24) that moves the middleware management to an event listener (one less ugly hack).

---------------------------------------------------------------------------

by fabpot at 2012-11-04T08:24:19Z

At this point, I want your feedback again about another possible move I'm not sure about.

Now that this PR introduces two event listener classes (`LocaleListener` and `MiddlewareListener`), what about creating two additional ones: one for the controller arguments converter and another one for the StringToResponse handling.

Moving them to proper listeners makes things more consistent (and everything would be registered in the dispatcher creation closure) and more customisable (not sure you ever need to modify the default behavior though).

What do you think?

---------------------------------------------------------------------------

by fabpot at 2012-11-04T08:45:07Z

I've just pushed a commit that implements my previous proposal (09a61047).

---------------------------------------------------------------------------

by igorw at 2012-11-04T15:14:53Z

👍 moving all of the subscribers out of the application class helps a lot to clean things up.

* `before` and `after` should be more consistent with the `HttpKernelInterface::MASTER_REQUEST` check.
* `error` is still a bit of a mess, if possible I'd move that to a separate class as well.

---------------------------------------------------------------------------

by fabpot at 2012-11-04T15:40:41Z

@igorw: The code in the error method has been moved to its own class (see 77a1878).

What do you mean about the consistency of the master request check?

---------------------------------------------------------------------------

by igorw at 2012-11-04T15:51:29Z

Error refactoring looks good.

The master request check should be return-early in both cases.
parents 5b945d5a 02a39976
Changelog Changelog
========= =========
* **2012-11-05**: Filters have been renamed to application middlewares in the
documentation.
* **2012-11-05**: The ``before()``, ``after()``, ``error()``, and ``finish()``
listener priorities now set the priority of the underlying Symfony event
instead of a custom one before.
* **2012-11-05**: Removing the default exception handler should now be done
via its ``disable()`` method:
Before:
unset($app['exception_handler']);
After:
$app['exception_handler']->disable();
* **2012-07-15**: removed the ``monolog.configure`` service. Use the * **2012-07-15**: removed the ``monolog.configure`` service. Use the
``extend`` method instead: ``extend`` method instead:
......
...@@ -48,7 +48,7 @@ Parsing the request body ...@@ -48,7 +48,7 @@ Parsing the request body
The request body should only be parsed as JSON if the ``Content-Type`` header The request body should only be parsed as JSON if the ``Content-Type`` header
begins with ``application/json``. Since we want to do this for every request, begins with ``application/json``. Since we want to do this for every request,
the easiest solution is to use a before filter. the easiest solution is to use an application before middleware.
We simply use ``json_decode`` to parse the content of the request and then We simply use ``json_decode`` to parse the content of the request and then
replace the request data on the ``$request`` object:: replace the request data on the ``$request`` object::
......
...@@ -6,6 +6,8 @@ Silex ...@@ -6,6 +6,8 @@ Silex
intro intro
usage usage
middlewares
modularity
services services
providers providers
testing testing
......
...@@ -28,7 +28,7 @@ to hook into the Symfony2 `HttpKernel ...@@ -28,7 +28,7 @@ to hook into the Symfony2 `HttpKernel
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_ <http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_
events. This allows fetching the ``Request``, converting string responses into events. This allows fetching the ``Request``, converting string responses into
``Response`` objects and handling Exceptions. We also use it to dispatch some ``Response`` objects and handling Exceptions. We also use it to dispatch some
custom events like before/after filters and errors. custom events like before/after middlewares and errors.
Controller Controller
~~~~~~~~~~ ~~~~~~~~~~
......
Middlewares
===========
Silex allows you to run code, that changes the default Silex behavior, at
different stages during the handling of a request through *middlewares*:
* *Application middlewares* are triggered independently of the current handled
request;
* *Route middlewares* are triggered when their associated route is matched.
Application Middlewares
-----------------------
The application middlewares are only run for the "master" Request.
Before Middleware
~~~~~~~~~~~~~~~~~
A *before* application middleware allows you to tweak the Request before the
controller is executed::
$app->before(function (Request $request) {
// ...
});
By default, the middleware is run after the routing and the security.
If you want your middleware to be run even if an exception is thrown early on
(on a 404 or 403 error for instance), then, you need to register it as an
early event::
$app->before(function (Request $request) {
// ...
}, Application::EARLY_EVENT);
Of course, in this case, the routing and the security won't have been
executed, and so you won't have access to the locale, the current route, or
the security user.
.. note::
The before middleware is an event registered on the Symfony *request*
event.
After Middleware
~~~~~~~~~~~~~~~~
An *after* application middleware allows you to tweak the Response before it
is sent to the client::
$app->after(function (Request $request, Response $response) {
// ...
});
.. note::
The after middleware is an event registered on the Symfony *response*
event.
Finish Middleware
~~~~~~~~~~~~~~~~~
A *finish* application middleware allows you to execute tasks after the
Response has been sent to the client (like sending emails or logging)::
$app->finish(function (Request $request, Response $response) {
// ...
// Warning: modifications to the Request or Response will be ignored
});
.. note::
The finish middleware is an event registered on the Symfony *terminate*
event.
Route Middlewares
-----------------
Route middlewares are added to routes or route collections and they are only
triggered when the corresponding route is matched. You can also stack them::
$app->get('/somewhere', function () {
// ...
})
->before($before1)
->before($before2)
->after($after1)
->after($after2)
;
Before Middleware
~~~~~~~~~~~~~~~~~
A *before* route middleware is fired just before the route callback, but after
the *before* application middlewares::
$before = function (Request $request) use ($app) {
// ...
};
$app->get('/somewhere', function () {
// ...
})
->before($before);
After Middleware
~~~~~~~~~~~~~~~~
An *after* route middleware is fired just after the route callback, but before
the application *after* application middlewares::
$after = function (Request $request, Response $response) use ($app) {
// ...
};
$app->get('/somewhere', function () {
// ...
})
->after($after);
Middlewares Priority
--------------------
You can add as many middlewares as you want, in which case they are triggered
in the same order as you added them.
You can explicitly control the priority of your middleware by passing an
additional argument to the registration methods::
$app->before(function (Request $request) {
// ...
}, 32);
As a convenience, two constants allow you to register an event as early as
possible or as late as possible::
$app->before(function (Request $request) {
// ...
}, Application::EARLY_EVENT);
$app->before(function (Request $request) {
// ...
}, Application::LATE_EVENT);
Short-circuiting the Controller
-------------------------------
If a before middleware returns a Response object, the Request handling is
short-circuited (the next middlewares won't be run, neither the route
callback), and the Response is passed to the after middlewares right away::
$app->before(function (Request $request) {
// redirect the user to the login screen if access to the Resource is protected
if (...) {
return new RedirectResponse('/login');
}
});
.. note::
If a before middleware does not return a Response or ``null``, a
``RuntimeException`` is thrown.
Modularity
==========
When your application starts to define too many controllers, you might want to
group them logically::
// define controllers for a blog
$blog = $app['controllers_factory'];
$blog->get('/', function () {
return 'Blog home page';
});
// ...
// define controllers for a forum
$forum = $app['controllers_factory'];
$forum->get('/', function () {
return 'Forum home page';
});
// define "global" controllers
$app->get('/', function () {
return 'Main home page';
});
$app->mount('/blog', $blog);
$app->mount('/forum', $forum);
.. note::
``$app['controllers_factory']`` is a factory that returns a new instance
of ``ControllerCollection`` when used.
``mount()`` prefixes all routes with the given prefix and merges them into the
main Application. So, ``/`` will map to the main home page, ``/blog/`` to the
blog home page, and ``/forum/`` to the forum home page.
.. caution::
When mounting a route collection under ``/blog``, it is not possible to
define a route for the ``/blog`` URL. The shortest possible URL is
``/blog/``.
.. note::
When calling ``get()``, ``match()``, or any other HTTP methods on the
Application, you are in fact calling them on a default instance of
``ControllerCollection`` (stored in ``$app['controllers']``).
Another benefit is the ability to apply settings on a set of controllers very
easily. Building on the example from the middleware section, here is how you
would secure all controllers for the backend collection::
$backend = $app['controllers_factory'];
// ensure that all controllers require logged-in users
$backend->before($mustBeLogged);
.. tip::
For a better readability, you can split each controller collection into a
separate file::
// blog.php
$blog = $app['controllers_factory'];
$blog->get('/', function () { return 'Blog home page'; });
return $blog;
// app.php
$app->mount('/blog', include 'blog.php');
Instead of requiring a file, you can also create a :doc:`Controller
provider </providers#controllers-providers>`.
...@@ -183,7 +183,8 @@ don't want to mess with most of them. ...@@ -183,7 +183,8 @@ don't want to mess with most of them.
$id = $app['request']->get('id'); $id = $app['request']->get('id');
This is only available when a request is being served, you can only access This is only available when a request is being served, you can only access
it from within a controller, before filter, after filter or error handler. it from within a controller, an application before/after middlewares, or an
error handler.
* **routes**: The `RouteCollection * **routes**: The `RouteCollection
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_ <http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
...@@ -213,7 +214,7 @@ don't want to mess with most of them. ...@@ -213,7 +214,7 @@ don't want to mess with most of them.
* **exception_handler**: The Exception handler is the default handler that is * **exception_handler**: The Exception handler is the default handler that is
used when you don't register one via the ``error()`` method or if your handler used when you don't register one via the ``error()`` method or if your handler
does not return a Response. Disable it with does not return a Response. Disable it with
``unset($app['exception_handler'])``. ``$app['exception_handler']->disable()``.
* **logger**: A * **logger**: A
`LoggerInterface `LoggerInterface
......
...@@ -99,7 +99,7 @@ executed before every test. ...@@ -99,7 +99,7 @@ executed before every test.
{ {
$app = require __DIR__.'/path/to/app.php'; $app = require __DIR__.'/path/to/app.php';
$app['debug'] = true; $app['debug'] = true;
unset($app['exception_handler']); $app['exception_handler']->disable();
return $app; return $app;
} }
......
...@@ -383,112 +383,6 @@ really be used. You can give a route a name by calling ``bind`` on the ...@@ -383,112 +383,6 @@ really be used. You can give a route a name by calling ``bind`` on the
It only makes sense to name routes if you use providers that make use of It only makes sense to name routes if you use providers that make use of
the ``RouteCollection``. the ``RouteCollection``.
Before, after and finish filters
--------------------------------
Silex allows you to run code before, after every request and even after the
response has been sent. This happens through ``before``, ``after`` and
``finish`` filters. All you need to do is pass a closure::
$app->before(function () {
// set up
});
$app->after(function () {
// tear down
});
$app->finish(function () {
// after response has been sent
});
The before filter has access to the current Request, and can short-circuit the
whole rendering by returning a Response::
$app->before(function (Request $request) {
// redirect the user to the login screen if access to the Resource is protected
if (...) {
return new RedirectResponse('/login');
}
});
The after filter has access to the Request and the Response::
$app->after(function (Request $request, Response $response) {
// tweak the Response
});
The finish filter has access to the Request and the Response::
$app->finish(function (Request $request, Response $response) {
// send e-mails ...
});
.. note::
The filters are only run for the "master" Request.
Route middlewares
-----------------
Route middlewares are PHP callables which are triggered when their associated
route is matched:
* ``before`` middlewares are fired just before the route callback, but after
the application ``before`` filters;
* ``after`` middlewares are fired just after the route callback, but before
the application ``after`` filters.
This can be used for a lot of use cases; for instance, here is a simple
"anonymous/logged user" check::
$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 () {
...
})
->before($mustBeAnonymous);
$app->get('/user/login', function () {
...
})
->before($mustBeAnonymous);
$app->get('/user/my-profile', function () {
...
})
->before($mustBeLogged);
The ``before`` and ``after`` methods can be called several times for a given
route, in which case they are triggered in the same order as you added them to
the route.
For convenience, the ``before`` middlewares are called with the current
``Request`` instance as an argument and the ``after`` middlewares are called
with the current ``Request`` and ``Response`` instance as arguments.
If any of the before middlewares returns a Symfony HTTP Response, it will
short-circuit the whole rendering: the next middlewares won't be run, neither
the route callback. You can also redirect to another page by returning a
redirect response, which you can create by calling the Application
``redirect`` method.
.. note::
If a before middleware does not return a Symfony HTTP Response or
``null``, a ``RuntimeException`` is thrown.
Global Configuration Global Configuration
-------------------- --------------------
...@@ -637,80 +531,6 @@ round-trip to the browser (as for a redirect), use an internal sub-request:: ...@@ -637,80 +531,6 @@ round-trip to the browser (as for a redirect), use an internal sub-request::
$request = Request::create($app['url_generator']->generate('hello'), 'GET'); $request = Request::create($app['url_generator']->generate('hello'), 'GET');
Modularity
----------
When your application starts to define too many controllers, you might want to
group them logically::
// define controllers for a blog
$blog = $app['controllers_factory'];
$blog->get('/', function () {
return 'Blog home page';
});
// ...
// define controllers for a forum
$forum = $app['controllers_factory'];
$forum->get('/', function () {
return 'Forum home page';
});
// define "global" controllers
$app->get('/', function () {
return 'Main home page';
});
$app->mount('/blog', $blog);
$app->mount('/forum', $forum);
.. note::
``$app['controllers_factory']`` is a factory that returns a new instance
of ``ControllerCollection`` when used.
``mount()`` prefixes all routes with the given prefix and merges them into the
main Application. So, ``/`` will map to the main home page, ``/blog/`` to the
blog home page, and ``/forum/`` to the forum home page.
.. caution::
When mounting a route collection under ``/blog``, it is not possible to
define a route for the ``/blog`` URL. The shortest possible URL is
``/blog/``.
.. note::
When calling ``get()``, ``match()``, or any other HTTP methods on the
Application, you are in fact calling them on a default instance of
``ControllerCollection`` (stored in ``$app['controllers']``).
Another benefit is the ability to apply settings on a set of controllers very
easily. Building on the example from the middleware section, here is how you
would secure all controllers for the backend collection::
$backend = $app['controllers_factory'];
// ensure that all controllers require logged-in users
$backend->before($mustBeLogged);
.. tip::
For a better readability, you can split each controller collection into a
separate file::
// blog.php
$blog = $app['controllers_factory'];
$blog->get('/', function () { return 'Blog home page'; });
return $blog;
// app.php
$app->mount('/blog', include 'blog.php');
Instead of requiring a file, you can also create a :doc:`Controller
provider </providers#controllers-providers>`.
JSON JSON
---- ----
......
...@@ -14,16 +14,11 @@ namespace Silex; ...@@ -14,16 +14,11 @@ namespace Silex;
use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface; use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent; use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\EventListener\LocaleListener;
use Symfony\Component\HttpKernel\EventListener\ResponseListener; use Symfony\Component\HttpKernel\EventListener\ResponseListener;
use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -32,24 +27,29 @@ use Symfony\Component\HttpFoundation\RedirectResponse; ...@@ -32,24 +27,29 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RequestContext;
use Silex\RedirectableUrlMatcher; use Silex\RedirectableUrlMatcher;
use Silex\ControllerResolver; use Silex\ControllerResolver;
use Silex\EventListener\LocaleListener;
use Silex\EventListener\MiddlewareListener;
use Silex\EventListener\ConverterListener;
use Silex\EventListener\StringToResponseListener;
/** /**
* The Silex framework class. * The Silex framework class.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Application extends \Pimple implements HttpKernelInterface, EventSubscriberInterface, TerminableInterface class Application extends \Pimple implements HttpKernelInterface, TerminableInterface
{ {
const VERSION = '1.0-DEV'; const VERSION = '1.0-DEV';
const EARLY_EVENT = 512;
const LATE_EVENT = -512;
private $providers = array(); private $providers = array();
private $booted = false; private $booted = false;
private $beforeDispatched = false;
/** /**
* Constructor. * Constructor.
...@@ -81,20 +81,26 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -81,20 +81,26 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
return new $app['route_class'](); return new $app['route_class']();
}; };
$this['exception_handler'] = $this->share(function () { $this['exception_handler'] = $this->share(function () use ($app) {
return new ExceptionHandler(); return new ExceptionHandler($app['debug']);
}); });
$this['dispatcher_class'] = 'Symfony\\Component\\EventDispatcher\\EventDispatcher'; $this['dispatcher_class'] = 'Symfony\\Component\\EventDispatcher\\EventDispatcher';
$this['dispatcher'] = $this->share(function () use ($app) { $this['dispatcher'] = $this->share(function () use ($app) {
$dispatcher = new $app['dispatcher_class'](); $dispatcher = new $app['dispatcher_class']();
$dispatcher->addSubscriber($app);
$urlMatcher = new LazyUrlMatcher(function () use ($app) { $urlMatcher = new LazyUrlMatcher(function () use ($app) {
return $app['url_matcher']; return $app['url_matcher'];
}); });
$dispatcher->addSubscriber(new RouterListener($urlMatcher, $app['request_context'], $app['logger'])); $dispatcher->addSubscriber(new RouterListener($urlMatcher, $app['request_context'], $app['logger']));
$dispatcher->addSubscriber(new LocaleListener($app['locale'], $urlMatcher)); $dispatcher->addSubscriber(new LocaleListener($app, $urlMatcher));
if (isset($app['exception_handler'])) {
$dispatcher->addSubscriber($app['exception_handler']);
}
$dispatcher->addSubscriber(new ResponseListener($app['charset']));
$dispatcher->addSubscriber(new MiddlewareListener($app));
$dispatcher->addSubscriber(new ConverterListener($app['routes']));
$dispatcher->addSubscriber(new StringToResponseListener());
return $dispatcher; return $dispatcher;
}); });
...@@ -120,42 +126,6 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -120,42 +126,6 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
return new RedirectableUrlMatcher($app['routes'], $app['request_context']); return new RedirectableUrlMatcher($app['routes'], $app['request_context']);
}); });
$this['route_before_middlewares_trigger'] = $this->protect(function (GetResponseEvent $event) use ($app) {
$request = $event->getRequest();
$routeName = $request->attributes->get('_route');
if (!$route = $app['routes']->get($routeName)) {
return;
}
foreach ((array) $route->getOption('_before_middlewares') as $callback) {
$ret = call_user_func($callback, $request, $app);
if ($ret instanceof Response) {
$event->setResponse($ret);
return;
} elseif (null !== $ret) {
throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
}
}
});
$this['route_after_middlewares_trigger'] = $this->protect(function (FilterResponseEvent $event) use ($app) {
$request = $event->getRequest();
$routeName = $request->attributes->get('_route');
if (!$route = $app['routes']->get($routeName)) {
return;
}
foreach ((array) $route->getOption('_after_middlewares') as $callback) {
$response = call_user_func($callback, $request, $event->getResponse());
if ($response instanceof Response) {
$event->setResponse($response);
} elseif (null !== $response) {
throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
}
}
});
$this['request_error'] = $this->protect(function () { $this['request_error'] = $this->protect(function () {
throw new \RuntimeException('Accessed request service outside of request scope. Try moving that call to a before handler or controller.'); throw new \RuntimeException('Accessed request service outside of request scope. Try moving that call to a before handler or controller.');
}); });
...@@ -281,7 +251,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -281,7 +251,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
*/ */
public function before($callback, $priority = 0) public function before($callback, $priority = 0)
{ {
$this['dispatcher']->addListener(SilexEvents::BEFORE, function (GetResponseEvent $event) use ($callback) { $this['dispatcher']->addListener(KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback) {
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$ret = call_user_func($callback, $event->getRequest()); $ret = call_user_func($callback, $event->getRequest());
if ($ret instanceof Response) { if ($ret instanceof Response) {
...@@ -301,7 +275,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -301,7 +275,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
*/ */
public function after($callback, $priority = 0) public function after($callback, $priority = 0)
{ {
$this['dispatcher']->addListener(SilexEvents::AFTER, function (FilterResponseEvent $event) use ($callback) { $this['dispatcher']->addListener(KernelEvents::RESPONSE, function (FilterResponseEvent $event) use ($callback) {
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
call_user_func($callback, $event->getRequest(), $event->getResponse()); call_user_func($callback, $event->getRequest(), $event->getResponse());
}, $priority); }, $priority);
} }
...@@ -317,7 +295,7 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -317,7 +295,7 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
*/ */
public function finish($callback, $priority = 0) public function finish($callback, $priority = 0)
{ {
$this['dispatcher']->addListener(SilexEvents::FINISH, function (PostResponseEvent $event) use ($callback) { $this['dispatcher']->addListener(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($callback) {
call_user_func($callback, $event->getRequest(), $event->getResponse()); call_user_func($callback, $event->getRequest(), $event->getResponse());
}, $priority); }, $priority);
} }
...@@ -349,40 +327,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -349,40 +327,11 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
* *
* @param mixed $callback Error handler callback, takes an Exception argument * @param mixed $callback Error handler callback, takes an Exception argument
* @param integer $priority The higher this value, the earlier an event * @param integer $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0) * listener will be triggered in the chain (defaults to -8)
*/ */
public function error($callback, $priority = 0) public function error($callback, $priority = -8)
{ {
$this['dispatcher']->addListener(SilexEvents::ERROR, function (GetResponseForExceptionEvent $event) use ($callback) { $this['dispatcher']->addListener(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($this, $callback), $priority);
$exception = $event->getException();
if (is_array($callback)) {
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (is_object($callback) && !$callback instanceof \Closure) {
$callbackReflection = new \ReflectionObject($callback);
$callbackReflection = $callbackReflection->getMethod('__invoke');
} else {
$callbackReflection = new \ReflectionFunction($callback);
}
if ($callbackReflection->getNumberOfParameters() > 0) {
$parameters = $callbackReflection->getParameters();
$expectedException = $parameters[0];
if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) {
return;
}
}
$code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500;
$result = call_user_func($callback, $exception, $code);
if (null !== $result) {
$response = $result instanceof Response ? $result : new Response((string) $result);
$event->setResponse($response);
}
}, $priority);
} }
/** /**
...@@ -498,8 +447,6 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -498,8 +447,6 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
$this->boot(); $this->boot();
} }
$this->beforeDispatched = false;
$current = HttpKernelInterface::SUB_REQUEST === $type ? $this['request'] : $this['request_error']; $current = HttpKernelInterface::SUB_REQUEST === $type ? $this['request'] : $this['request_error'];
$this['request'] = $request; $this['request'] = $request;
...@@ -520,147 +467,4 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -520,147 +467,4 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
{ {
$this['kernel']->terminate($request, $response); $this['kernel']->terminate($request, $response);
} }
/**
* Handles KernelEvents::REQUEST events registered early.
*
* @param GetResponseEvent $event The event to handle
*/
public function onEarlyKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
if (isset($this['exception_handler'])) {
$this['dispatcher']->addSubscriber($this['exception_handler']);
}
$this['dispatcher']->addSubscriber(new ResponseListener($this['charset']));
}
}
/**
* Runs before filters.
*
* @param GetResponseEvent $event The event to handle
*
* @see before()
*/
public function onKernelRequest(GetResponseEvent $event)
{
$this['locale'] = $event->getRequest()->getLocale();
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$this->beforeDispatched = true;
$this['dispatcher']->dispatch(SilexEvents::BEFORE, $event);
if ($event->hasResponse()) {
return;
}
}
$this['route_before_middlewares_trigger']($event);
}
/**
* Handles converters.
*
* @param FilterControllerEvent $event The event to handle
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$route = $this['routes']->get($request->attributes->get('_route'));
if ($route && $converters = $route->getOption('_converters')) {
foreach ($converters as $name => $callback) {
$request->attributes->set($name, call_user_func($callback, $request->attributes->get($name, null), $request));
}
}
}
/**
* Handles string responses.
*
* @param GetResponseForControllerResultEvent $event The event to handle
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$response = $event->getControllerResult();
$response = $response instanceof Response ? $response : new Response((string) $response);
$event->setResponse($response);
}
/**
* Runs after filters.
*
* @param FilterResponseEvent $event The event to handle
*
* @see after()
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$this['route_after_middlewares_trigger']($event);
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$this['dispatcher']->dispatch(SilexEvents::AFTER, $event);
}
}
/**
* Runs finish filters.
*
* @param PostResponseEvent $event The event to handle
*
* @see finish()
*/
public function onKernelTerminate(PostResponseEvent $event)
{
$this['dispatcher']->dispatch(SilexEvents::FINISH, $event);
}
/**
* Runs error filters.
*
* Executes registered error handlers until a response is returned,
* in which case it returns it to the client.
*
* @param GetResponseForExceptionEvent $event The event to handle
*
* @see error()
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (!$this->beforeDispatched) {
$this->beforeDispatched = true;
try {
$this['dispatcher']->dispatch(SilexEvents::BEFORE, $event);
} catch (\Exception $e) {
// as we are already handling an exception, ignore this one
// even if it might have been thrown on purpose by the developer
}
}
$errorEvent = new GetResponseForExceptionEvent($this, $event->getRequest(), $event->getRequestType(), $event->getException());
$this['dispatcher']->dispatch(SilexEvents::ERROR, $errorEvent);
if ($errorEvent->hasResponse()) {
$event->setResponse($errorEvent->getResponse());
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(
array('onEarlyKernelRequest', 256),
array('onKernelRequest')
),
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
KernelEvents::EXCEPTION => array('onKernelException', -10),
KernelEvents::TERMINATE => 'onKernelTerminate',
KernelEvents::VIEW => array('onKernelView', -10),
);
}
} }
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\EventListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Handles converters.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConverterListener implements EventSubscriberInterface
{
protected $routes;
/**
* Constructor.
*
* @param RouteCollection $routes A RouteCollection instance
*/
public function __construct(RouteCollection $routes)
{
$this->routes = $routes;
}
/**
* Handles converters.
*
* @param FilterControllerEvent $event The event to handle
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$route = $this->routes->get($request->attributes->get('_route'));
if ($route && $converters = $route->getOption('_converters')) {
foreach ($converters as $name => $callback) {
$request->attributes->set($name, call_user_func($callback, $request->attributes->get($name), $request));
}
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\EventListener\LocaleListener as BaseLocaleListener;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Silex\Application;
/**
* Initializes the locale based on the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LocaleListener extends BaseLocaleListener
{
protected $app;
public function __construct(Application $app, RequestContextAwareInterface $router = null)
{
parent::__construct($app['locale'], $router);
$this->app = $app;
}
public function onKernelRequest(GetResponseEvent $event)
{
parent::onKernelRequest($event);
$this->app['locale'] = $event->getRequest()->getLocale();
}
}
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\EventListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Silex\Application;
/**
* Manages the route middlewares.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MiddlewareListener implements EventSubscriberInterface
{
protected $app;
/**
* Constructor.
*
* @param Application $app An Application instance
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Runs before filters.
*
* @param GetResponseEvent $event The event to handle
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$routeName = $request->attributes->get('_route');
if (!$route = $this->app['routes']->get($routeName)) {
return;
}
foreach ((array) $route->getOption('_before_middlewares') as $callback) {
$ret = call_user_func($callback, $request, $this->app);
if ($ret instanceof Response) {
$event->setResponse($ret);
return;
} elseif (null !== $ret) {
throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
}
}
}
/**
* Runs after filters.
*
* @param FilterResponseEvent $event The event to handle
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
$routeName = $request->attributes->get('_route');
if (!$route = $this->app['routes']->get($routeName)) {
return;
}
foreach ((array) $route->getOption('_after_middlewares') as $callback) {
$response = call_user_func($callback, $request, $event->getResponse(), $this->app);
if ($response instanceof Response) {
$event->setResponse($response);
} elseif (null !== $response) {
throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
}
}
}
public static function getSubscribedEvents()
{
return array(
// this must be executed after the late events defined with before() (and their priority is 512)
KernelEvents::REQUEST => array('onKernelRequest', -1024),
KernelEvents::RESPONSE => array('onKernelResponse', 128),
);
}
}
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\EventListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Converts string responses to proper Response instances.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StringToResponseListener implements EventSubscriberInterface
{
/**
* Handles string responses.
*
* @param GetResponseForControllerResultEvent $event The event to handle
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$response = $event->getControllerResult();
if (!(
null === $response
|| is_array($response)
|| $response instanceof Response
|| (is_object($response) && !method_exists($response, '__toString'))
)) {
$event->setResponse(new Response((string) $response));
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::VIEW => array('onKernelView', -10),
);
}
}
...@@ -14,6 +14,7 @@ namespace Silex; ...@@ -14,6 +14,7 @@ namespace Silex;
use Symfony\Component\HttpKernel\Debug\ExceptionHandler as DebugExceptionHandler; use Symfony\Component\HttpKernel\Debug\ExceptionHandler as DebugExceptionHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/** /**
* Defaults exception handler. * Defaults exception handler.
...@@ -22,10 +23,27 @@ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; ...@@ -22,10 +23,27 @@ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
*/ */
class ExceptionHandler implements EventSubscriberInterface class ExceptionHandler implements EventSubscriberInterface
{ {
protected $debug;
protected $enabled;
public function __construct($debug)
{
$this->debug = $debug;
$this->enabled = true;
}
public function disable()
{
$this->enabled = false;
}
public function onSilexError(GetResponseForExceptionEvent $event) public function onSilexError(GetResponseForExceptionEvent $event)
{ {
$app = $event->getKernel(); if (!$this->enabled) {
$handler = new DebugExceptionHandler($app['debug']); return;
}
$handler = new DebugExceptionHandler($this->debug);
$event->setResponse($handler->createResponse($event->getException())); $event->setResponse($handler->createResponse($event->getException()));
} }
...@@ -35,6 +53,6 @@ class ExceptionHandler implements EventSubscriberInterface ...@@ -35,6 +53,6 @@ class ExceptionHandler implements EventSubscriberInterface
*/ */
public static function getSubscribedEvents() public static function getSubscribedEvents()
{ {
return array(SilexEvents::ERROR => array('onSilexError', -255)); return array(KernelEvents::EXCEPTION => array('onSilexError', -255));
} }
} }
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Silex\Application;
/**
* Wraps exception listeners.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExceptionListenerWrapper
{
protected $app;
protected $callback;
/**
* Constructor.
*
* @param Application $app An Application instance
*/
public function __construct(Application $app, $callback)
{
$this->app = $app;
$this->callback = $callback;
}
public function __invoke(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if (!$this->shouldRun($exception)) {
return;
}
$code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500;
$response = call_user_func($this->callback, $exception, $code);
$this->ensureResponse($response, $event);
}
protected function shouldRun(\Exception $exception)
{
if (is_array($this->callback)) {
$callbackReflection = new \ReflectionMethod($this->callback[0], $this->callback[1]);
} elseif (is_object($this->callback) && !$this->callback instanceof \Closure) {
$callbackReflection = new \ReflectionObject($this->callback);
$callbackReflection = $callbackReflection->getMethod('__invoke');
} else {
$callbackReflection = new \ReflectionFunction($this->callback);
}
if ($callbackReflection->getNumberOfParameters() > 0) {
$parameters = $callbackReflection->getParameters();
$expectedException = $parameters[0];
if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) {
return false;
}
}
return true;
}
protected function ensureResponse($response, GetResponseForExceptionEvent $event)
{
if ($response instanceof Response) {
$event->setResponse($response);
} else {
$viewEvent = new GetResponseForControllerResultEvent($this->app['kernel'], $event->getRequest(), $event->getRequestType(), $response);
$this->app['dispatcher']->dispatch(KernelEvents::VIEW, $viewEvent);
if ($viewEvent->hasResponse()) {
$event->setResponse($viewEvent->getResponse());
}
}
}
}
...@@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider; ...@@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\Forms; use Symfony\Component\Form\Forms;
/** /**
......
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex;
/**
* The Silex events.
*
* @author Igor Wiedler <igor@wiedler.ch>
*/
final class SilexEvents
{
const BEFORE = 'silex.before';
const AFTER = 'silex.after';
const FINISH = 'silex.finish';
const ERROR = 'silex.error';
}
...@@ -147,7 +147,7 @@ class BeforeAfterFilterTest extends \PHPUnit_Framework_TestCase ...@@ -147,7 +147,7 @@ class BeforeAfterFilterTest extends \PHPUnit_Framework_TestCase
$app->before(function () use (&$i) { $app->before(function () use (&$i) {
$i++; $i++;
}); }, Application::EARLY_EVENT);
$app->after(function () use (&$i) { $app->after(function () use (&$i) {
$i++; $i++;
...@@ -193,18 +193,18 @@ class BeforeAfterFilterTest extends \PHPUnit_Framework_TestCase ...@@ -193,18 +193,18 @@ class BeforeAfterFilterTest extends \PHPUnit_Framework_TestCase
$app = new Application(); $app = new Application();
$app->before(function () use ($app) { $app->before(function () use ($app) {
$app['locale'] = $app['request']->get('locale'); $app['project'] = $app['request']->get('project');
}); });
$app->match('/foo/{locale}', function () use ($app) { $app->match('/foo/{project}', function () use ($app) {
return $app['locale']; return $app['project'];
}); });
$request = Request::create('/foo/en'); $request = Request::create('/foo/bar');
$this->assertEquals('en', $app->handle($request)->getContent()); $this->assertEquals('bar', $app->handle($request)->getContent());
$request = Request::create('/foo/de'); $request = Request::create('/foo/baz');
$this->assertEquals('de', $app->handle($request)->getContent()); $this->assertEquals('baz', $app->handle($request)->getContent());
} }
public function testBeforeFilterAccessesRequestAndCanReturnResponse() public function testBeforeFilterAccessesRequestAndCanReturnResponse()
......
...@@ -108,7 +108,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase ...@@ -108,7 +108,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
public function testNoErrorHandler() public function testNoErrorHandler()
{ {
$app = new Application(); $app = new Application();
unset($app['exception_handler']); $app['exception_handler']->disable();
$app->match('/foo', function () { $app->match('/foo', function () {
throw new \RuntimeException('foo exception'); throw new \RuntimeException('foo exception');
...@@ -190,7 +190,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase ...@@ -190,7 +190,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
public function testNoResponseErrorHandler() public function testNoResponseErrorHandler()
{ {
$app = new Application(); $app = new Application();
unset($app['exception_handler']); $app['exception_handler']->disable();
$app->match('/foo', function () { $app->match('/foo', function () {
throw new \RuntimeException('foo exception'); throw new \RuntimeException('foo exception');
...@@ -262,7 +262,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase ...@@ -262,7 +262,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
// just making sure the dispatcher gets created // just making sure the dispatcher gets created
}); });
unset($app['exception_handler']); $app['exception_handler']->disable();
try { try {
$request = Request::create('/foo'); $request = Request::create('/foo');
......
...@@ -44,11 +44,11 @@ class HttpCacheServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -44,11 +44,11 @@ class HttpCacheServiceProviderTest extends \PHPUnit_Framework_TestCase
{ {
$finished = false; $finished = false;
$app->get('/', function () use ($app, &$finished) { $app->finish(function () use (&$finished) {
$app->finish(function () use (&$finished) { $finished = true;
$finished = true; });
});
$app->get('/', function () use ($app) {
return new UnsendableResponse('will do something after finish'); return new UnsendableResponse('will do something after finish');
}); });
......
...@@ -102,8 +102,7 @@ class RouterTest extends \PHPUnit_Framework_TestCase ...@@ -102,8 +102,7 @@ class RouterTest extends \PHPUnit_Framework_TestCase
public function testMissingRoute() public function testMissingRoute()
{ {
$app = new Application(); $app = new Application();
$app['exception_handler']->disable();
unset($app['exception_handler']);
$request = Request::create('/baz'); $request = Request::create('/baz');
$app->handle($request); $app->handle($request);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment