Add a middleware dispatch #32

Merged
merged 3 commits into from Oct 12, 2015
View
@@ -19,6 +19,7 @@
"zendframework/zend-hydrator": "~1.0",
"zendframework/zend-form": "~2.6",
"zendframework/zend-stdlib": "~2.7",
+ "zendframework/zend-psr7bridge": "^0.2",
"container-interop/container-interop": "^1.1"
},
"require-dev": {
View
@@ -28,6 +28,7 @@
* - RouteListener
* - Router
* - DispatchListener
+ * - MiddlewareListener
* - ViewManager
*
* The most common workflow is:
@@ -53,6 +54,7 @@ class Application implements
const ERROR_CONTROLLER_INVALID = 'error-controller-invalid';
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 +68,7 @@ class Application implements
*/
protected $defaultListeners = [
'RouteListener',
+ 'MiddlewareListener',
'DispatchListener',
'HttpMethodListener',
'ViewManager',
@@ -63,10 +63,10 @@ public function attach(EventManagerInterface $events, $priority = 1)
public function onDispatch(MvcEvent $e)
{
$routeMatch = $e->getRouteMatch();
- $controllerName = $routeMatch->getParam('controller', 'not-found');
$application = $e->getApplication();
- $events = $application->getEventManager();
- $controllerLoader = $application->getServiceManager()->get('ControllerManager');
+ $serviceManager = $application->getServiceManager();
+ $controllerName = $routeMatch->getParam('controller', 'not-found');
+ $controllerLoader = $serviceManager->get('ControllerManager');
if (!$controllerLoader->has($controllerName)) {
$return = $this->marshalControllerNotFoundEvent($application::ERROR_CONTROLLER_NOT_FOUND, $controllerName, $e, $application);
@@ -83,13 +83,13 @@ public function onDispatch(MvcEvent $e)
return $this->complete($return, $e);
}
- $request = $e->getRequest();
- $response = $application->getResponse();
-
if ($controller instanceof InjectApplicationEventInterface) {
$controller->setEvent($e);
}
+ $request = $e->getRequest();
+ $response = $application->getResponse();
+
try {
$return = $controller->dispatch($request, $response);
} catch (\Exception $ex) {
@@ -99,7 +99,7 @@ public function onDispatch(MvcEvent $e)
$e->setControllerClass(get_class($controller));
$e->setParam('exception', $ex);
- $return = $events->triggerEvent($e)->last();
+ $return = $application->getEventManager()->triggerEvent($e)->last();
if (! $return) {
$return = $e->getResult();
}
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace Zend\Mvc;
+
+use ArrayObject;
+use Zend\EventManager\AbstractListenerAggregate;
+use Zend\EventManager\EventManagerInterface;
+use Zend\Mvc\Exception\InvalidControllerException;
+use Zend\Stdlib\ArrayUtils;
+use Zend\Psr7Bridge\Psr7ServerRequest as Psr7Request;
+use Zend\Psr7Bridge\Psr7Response;
+
+class MiddlewareListener extends AbstractListenerAggregate
+{
+ /**
+ * Attach listeners to an event manager
+ *
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 1);
+ }
+
+ /**
+ * 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) {
@samsonasik
samsonasik Oct 6, 2015 Contributor

move to use statement, I created PR to your branch for it ezimuel#1

+ $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;
+ }
+}
@@ -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',
@@ -163,6 +163,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'],
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace ZendTest\Mvc;
+
+use PHPUnit_Framework_TestCase as TestCase;
+use Zend\Mvc\Application;
+use Zend\Mvc\MvcEvent;
+use Zend\Mvc\Router;
+use Zend\Mvc\Service\ServiceManagerConfig;
+use Zend\Mvc\Service\ServiceListenerFactory;
+use Zend\ServiceManager\ServiceManager;
+use Zend\Stdlib\ArrayUtils;
+
+class MiddlewareListenerTest extends TestCase
+{
+ /**
+ * @var ServiceManager
+ */
+ protected $serviceManager;
+
+ /**
+ * @var Application
+ */
+ protected $application;
+
+ public function setUp()
+ {
+ $serviceConfig = ArrayUtils::merge(
+ $this->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();
+ });
+
+ $return = $this->application->run();
+ $response = $return->getResponse();
+
+ $this->assertEmpty($log);
+ $this->assertInstanceOf('Zend\Http\Response', $response);
+ $this->assertSame(200, $response->getStatusCode());
+ $this->assertEquals('Test!', $response->getBody());
+ }
+}