diff --git a/src/Application.php b/src/Application.php index 18a839ba2..592b8400a 100644 --- a/src/Application.php +++ b/src/Application.php @@ -27,6 +27,7 @@ * - RouteListener * - Router * - DispatchListener + * - MiddlewareListener * - ViewManager * * The most common workflow is: @@ -53,7 +54,7 @@ class Application implements const ERROR_EXCEPTION = 'error-exception'; const ERROR_ROUTER_NO_MATCH = 'error-router-no-match'; const ERROR_MIDDLEWARE_CANNOT_DISPATCH = 'error-middleware-cannot-dispatch'; - + /** * @var array */ @@ -66,6 +67,7 @@ class Application implements */ protected $defaultListeners = [ 'RouteListener', + 'MiddlewareListener', 'DispatchListener', 'HttpMethodListener', 'ViewManager', diff --git a/src/DispatchListener.php b/src/DispatchListener.php index 6a81d7f09..1e694ecd5 100644 --- a/src/DispatchListener.php +++ b/src/DispatchListener.php @@ -14,8 +14,6 @@ use Zend\EventManager\EventManagerInterface; use Zend\Mvc\Exception\InvalidControllerException; use Zend\Stdlib\ArrayUtils; -use Zend\Psr7Bridge\Psr7ServerRequest as Psr7Request; -use Zend\Psr7Bridge\Psr7Response; /** * Default dispatch listener @@ -63,29 +61,10 @@ public function attach(EventManagerInterface $events) */ public function onDispatch(MvcEvent $e) { - $routeMatch = $e->getRouteMatch(); - $request = $e->getRequest(); - $application = $e->getApplication(); - $response = $application->getResponse(); - $serviceManager = $application->getServiceManager(); - - // middleware? - $middleware = $routeMatch->getParam('middleware', false); - if (false !== $middleware) { - if (is_string($middleware) && $serviceManager->has($middleware)) { - $middleware = $serviceManager->get($middleware); - } - if (!is_callable($middleware)) { - $middleware = is_string($middleware) ? $middleware : get_class($middleware); - $return = $this->marshalControllerNotFoundEvent($application::ERROR_MIDDLEWARE_CANNOT_DISPATCH, $middleware, $e, $application); - return $this->complete($return, $e); - } - $return = $middleware(Psr7Request::fromZend($request), Psr7Response::fromZend($response)); - return $this->complete(Psr7Response::toZend($return), $e); - } - + $routeMatch = $e->getRouteMatch(); + $application = $e->getApplication(); + $serviceManager = $application->getServiceManager(); $controllerName = $routeMatch->getParam('controller', 'not-found'); - $events = $application->getEventManager(); $controllerLoader = $serviceManager->get('ControllerManager'); if (!$controllerLoader->has($controllerName)) { @@ -107,6 +86,9 @@ public function onDispatch(MvcEvent $e) $controller->setEvent($e); } + $request = $e->getRequest(); + $response = $application->getResponse(); + try { $return = $controller->dispatch($request, $response); } catch (\Exception $ex) { @@ -114,7 +96,7 @@ public function onDispatch(MvcEvent $e) ->setController($controllerName) ->setControllerClass(get_class($controller)) ->setParam('exception', $ex); - $results = $events->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $e); + $results = $application->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $e); $return = $results->last(); if (! $return) { $return = $e->getResult(); diff --git a/src/MiddlewareListener.php b/src/MiddlewareListener.php new file mode 100644 index 000000000..8e6f10850 --- /dev/null +++ b/src/MiddlewareListener.php @@ -0,0 +1,116 @@ +listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 1000); + } + + /** + * Listen to the "dispatch" event + * + * @param MvcEvent $e + * @return mixed + */ + public function onDispatch(MvcEvent $e) + { + $routeMatch = $e->getRouteMatch(); + $middleware = $routeMatch->getParam('middleware', false); + if (false === $middleware) { + return; + } + + $request = $e->getRequest(); + $application = $e->getApplication(); + $response = $application->getResponse(); + $serviceManager = $application->getServiceManager(); + $middlewareName = is_string($middleware) ? $middleware : get_class($middleware); + + if (is_string($middleware) && $serviceManager->has($middleware)) { + $middleware = $serviceManager->get($middleware); + } + if (!is_callable($middleware)) { + $return = $this->marshalMiddlewareNotCallable($application::ERROR_MIDDLEWARE_CANNOT_DISPATCH, $middlewareName, $e, $application); + $e->setResult($return); + return $return; + } + try { + $return = $middleware(Psr7Request::fromZend($request), Psr7Response::fromZend($response)); + } catch (\Exception $ex) { + $e->setError($application::ERROR_EXCEPTION) + ->setController($middlewareName) + ->setControllerClass(get_class($middleware)) + ->setParam('exception', $ex); + $results = $events->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $e); + $return = $results->last(); + if (! $return) { + $return = $e->getResult(); + } + } + + if (! $return instanceof \Psr\Http\Message\ResponseInterface) { + $e->setResult($return); + return $return; + } + $response = Psr7Response::toZend($return); + $e->setResult($response); + return $response; + } + + /** + * Marshal a middleware not callable exception event + * + * @param string $type + * @param string $middlewareName + * @param MvcEvent $event + * @param Application $application + * @param \Exception $exception + * @return mixed + */ + protected function marshalMiddlewareNotCallable( + $type, + $middlewareName, + MvcEvent $event, + Application $application, + \Exception $exception = null + ) { + $event->setError($type) + ->setController($middlewareName) + ->setControllerClass('Middleware not callable: ' . $middlewareName); + if ($exception !== null) { + $event->setParam('exception', $exception); + } + + $events = $application->getEventManager(); + $results = $events->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $event); + $return = $results->last(); + if (! $return) { + $return = $event->getResult(); + } + return $return; + } +} diff --git a/src/Service/ServiceListenerFactory.php b/src/Service/ServiceListenerFactory.php index 299c2b3f0..6aedc12ee 100644 --- a/src/Service/ServiceListenerFactory.php +++ b/src/Service/ServiceListenerFactory.php @@ -36,6 +36,7 @@ class ServiceListenerFactory implements FactoryInterface protected $defaultServiceConfig = [ 'invokables' => [ 'DispatchListener' => 'Zend\Mvc\DispatchListener', + 'MiddlewareListener' => 'Zend\Mvc\MiddlewareListener', 'RouteListener' => 'Zend\Mvc\RouteListener', 'SendResponseListener' => 'Zend\Mvc\SendResponseListener', 'ViewJsonRenderer' => 'Zend\View\Renderer\JsonRenderer', diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index bc2960b19..e34e9a080 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -166,6 +166,7 @@ public function bootstrapRegistersListenersProvider() return [ ['RouteListener', MvcEvent::EVENT_ROUTE, 'onRoute'], ['DispatchListener', MvcEvent::EVENT_DISPATCH, 'onDispatch'], + ['MiddlewareListener', MvcEvent::EVENT_DISPATCH, 'onDispatch'], ['SendResponseListener', MvcEvent::EVENT_FINISH, 'sendResponse'], ['ViewManager', MvcEvent::EVENT_BOOTSTRAP, 'onBootstrap'], ['HttpMethodListener', MvcEvent::EVENT_ROUTE, 'onRoute'], diff --git a/test/DispatchListenerTest.php b/test/DispatchListenerTest.php index cbf27648d..d7b84d614 100644 --- a/test/DispatchListenerTest.php +++ b/test/DispatchListenerTest.php @@ -122,44 +122,4 @@ public function testUnlocatableControllerLoaderComposedOfAbstractFactory() $this->assertArrayHasKey('error', $log); $this->assertSame('error-controller-not-found', $log['error']); } - - public function testMiddlewareDispatch() - { - $request = $this->serviceManager->get('Request'); - $request->setUri('http://example.local/path'); - - $router = $this->serviceManager->get('HttpRouter'); - $route = Router\Http\Literal::factory([ - 'route' => '/path', - 'defaults' => [ - 'middleware' => function($request, $response) { - $this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $request); - $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $response); - $response->getBody()->write('Test!'); - return $response; - } - ], - ]); - $router->addRoute('path', $route); - $this->application->bootstrap(); - - $controllerLoader = $this->serviceManager->get('ControllerLoader'); - $controllerLoader->addAbstractFactory('ZendTest\Mvc\Controller\TestAsset\ControllerLoaderAbstractFactory'); - - $log = []; - $this->application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) use (&$log) { - $log['error'] = $e->getError(); - }); - - $this->application->run(); - - $event = $this->application->getMvcEvent(); - $dispatchListener = $this->serviceManager->get('DispatchListener'); - $return = $dispatchListener->onDispatch($event); - - $this->assertEmpty($log); - $this->assertInstanceOf('Zend\Http\Response', $return); - $this->assertSame(200, $return->getStatusCode()); - $this->assertEquals('Test!', $return->getBody()); - } } diff --git a/test/MiddlewareListenerTest.php b/test/MiddlewareListenerTest.php new file mode 100644 index 000000000..30deeea9a --- /dev/null +++ b/test/MiddlewareListenerTest.php @@ -0,0 +1,111 @@ +readAttribute(new ServiceListenerFactory, 'defaultServiceConfig'), + [ + 'allow_override' => true, + 'invokables' => [ + 'Request' => 'Zend\Http\PhpEnvironment\Request', + 'Response' => 'Zend\Http\PhpEnvironment\Response', + 'ViewManager' => 'ZendTest\Mvc\TestAsset\MockViewManager', + 'SendResponseListener' => 'ZendTest\Mvc\TestAsset\MockSendResponseListener', + 'BootstrapListener' => 'ZendTest\Mvc\TestAsset\StubBootstrapListener', + ], + 'aliases' => [ + 'Router' => 'HttpRouter', + ], + 'services' => [ + 'Config' => [], + 'ApplicationConfig' => [ + 'modules' => [], + 'module_listener_options' => [ + 'config_cache_enabled' => false, + 'cache_dir' => 'data/cache', + 'module_paths' => [], + ], + ], + ], + ] + ); + $this->serviceManager = new ServiceManager(new ServiceManagerConfig($serviceConfig)); + $this->application = $this->serviceManager->get('Application'); + } + + public function setupPathMiddleware() + { + $request = $this->serviceManager->get('Request'); + $request->setUri('http://example.local/path'); + + $router = $this->serviceManager->get('HttpRouter'); + $route = Router\Http\Literal::factory([ + 'route' => '/path', + 'defaults' => [ + 'middleware' => function($request, $response) { + $this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $request); + $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $response); + $response->getBody()->write('Test!'); + return $response; + } + ], + ]); + $router->addRoute('path', $route); + $this->application->bootstrap(); + } + + + public function testMiddlewareDispatch() + { + $this->setupPathMiddleware(); + + $controllerLoader = $this->serviceManager->get('ControllerLoader'); + $controllerLoader->addAbstractFactory('ZendTest\Mvc\Controller\TestAsset\ControllerLoaderAbstractFactory'); + + $log = []; + $this->application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) use (&$log) { + $log['error'] = $e->getError(); + }); + + $this->application->run(); + + $event = $this->application->getMvcEvent(); + $dispatchListener = $this->serviceManager->get('DispatchListener'); + $return = $dispatchListener->onDispatch($event); + + $this->assertEmpty($log); + $this->assertInstanceOf('Zend\Http\Response', $return); + $this->assertSame(200, $return->getStatusCode()); + $this->assertEquals('Test!', $return->getBody()); + } +}