Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 367045a

Browse files
committed
Merge branch 'feature/32' into develop
Close #32
2 parents 334012c + b9d706d commit 367045a

9 files changed

+271
-24
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ All notable changes to this project will be documented in this file, in reverse
1212
- [#36](https://github.com/zendframework/zend-mvc/pull/36) adds more than a
1313
dozen service factories, primarily to separate conditional factories into
1414
discrete factories.
15+
- [#32](https://github.com/zendframework/zend-mvc/pull/32) adds
16+
`Zend\Mvc\MiddlewareListener`, which allows dispatching PSR-7-based middleware
17+
implementing the signature `function (ServerRequestInterface $request,
18+
ResponseInterface $response)`. To dispatch such middleware, point the
19+
`middleware` "default" for a given route to a service name or callable that
20+
will resolve to the middleware:
21+
22+
```php
23+
[ 'router' => 'routes' => [
24+
'path' => [
25+
'type' => 'Literal',
26+
'options' => [
27+
'route' => '/path',
28+
'defaults' => [
29+
'middleware' => 'ServiceNameForPathMiddleware',
30+
],
31+
],
32+
],
33+
]
34+
```
35+
36+
This new listener listens at the same priority as the `DispatchListener`, but,
37+
due to being registered earlier, will invoke first; if the route match does
38+
not resolve to middleware, it will fall through to the original
39+
`DispatchListener`, allowing normal ZF2-style controller dispatch.
1540

1641
### Deprecated
1742

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"zendframework/zend-hydrator": "~1.0",
2020
"zendframework/zend-form": "~2.6",
2121
"zendframework/zend-stdlib": "~2.7",
22+
"zendframework/zend-psr7bridge": "^0.2",
2223
"container-interop/container-interop": "^1.1"
2324
},
2425
"require-dev": {

src/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* - RouteListener
2929
* - Router
3030
* - DispatchListener
31+
* - MiddlewareListener
3132
* - ViewManager
3233
*
3334
* The most common workflow is:
@@ -53,6 +54,7 @@ class Application implements
5354
const ERROR_CONTROLLER_INVALID = 'error-controller-invalid';
5455
const ERROR_EXCEPTION = 'error-exception';
5556
const ERROR_ROUTER_NO_MATCH = 'error-router-no-match';
57+
const ERROR_MIDDLEWARE_CANNOT_DISPATCH = 'error-middleware-cannot-dispatch';
5658

5759
/**
5860
* @var array
@@ -66,6 +68,7 @@ class Application implements
6668
*/
6769
protected $defaultListeners = [
6870
'RouteListener',
71+
'MiddlewareListener',
6972
'DispatchListener',
7073
'HttpMethodListener',
7174
'ViewManager',

src/DispatchListener.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ public function onDispatch(MvcEvent $e)
100100
return $this->complete($return, $e);
101101
}
102102

103-
$request = $e->getRequest();
104-
$response = $application->getResponse();
105-
106103
if ($controller instanceof InjectApplicationEventInterface) {
107104
$controller->setEvent($e);
108105
}
109106

107+
$request = $e->getRequest();
108+
$response = $application->getResponse();
109+
110110
try {
111111
$return = $controller->dispatch($request, $response);
112112
} catch (\Exception $ex) {
@@ -116,7 +116,7 @@ public function onDispatch(MvcEvent $e)
116116
$e->setControllerClass(get_class($controller));
117117
$e->setParam('exception', $ex);
118118

119-
$return = $events->triggerEvent($e)->last();
119+
$return = $application->getEventManager()->triggerEvent($e)->last();
120120
if (! $return) {
121121
$return = $e->getResult();
122122
}

src/MiddlewareListener.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace Zend\Mvc;
11+
12+
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
13+
use Zend\EventManager\AbstractListenerAggregate;
14+
use Zend\EventManager\EventManagerInterface;
15+
use Zend\Psr7Bridge\Psr7ServerRequest as Psr7Request;
16+
use Zend\Psr7Bridge\Psr7Response;
17+
18+
class MiddlewareListener extends AbstractListenerAggregate
19+
{
20+
/**
21+
* Attach listeners to an event manager
22+
*
23+
* @param EventManagerInterface $events
24+
* @return void
25+
*/
26+
public function attach(EventManagerInterface $events, $priority = 1)
27+
{
28+
$this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 1);
29+
}
30+
31+
/**
32+
* Listen to the "dispatch" event
33+
*
34+
* @param MvcEvent $event
35+
* @return mixed
36+
*/
37+
public function onDispatch(MvcEvent $event)
38+
{
39+
$routeMatch = $event->getRouteMatch();
40+
$middleware = $routeMatch->getParam('middleware', false);
41+
if (false === $middleware) {
42+
return;
43+
}
44+
45+
$request = $event->getRequest();
46+
$application = $event->getApplication();
47+
$response = $application->getResponse();
48+
$serviceManager = $application->getServiceManager();
49+
$middlewareName = is_string($middleware) ? $middleware : get_class($middleware);
50+
51+
if (is_string($middleware) && $serviceManager->has($middleware)) {
52+
$middleware = $serviceManager->get($middleware);
53+
}
54+
if (! is_callable($middleware)) {
55+
$return = $this->marshalMiddlewareNotCallable($application::ERROR_MIDDLEWARE_CANNOT_DISPATCH, $middlewareName, $event, $application);
56+
$event->setResult($return);
57+
return $return;
58+
}
59+
try {
60+
$return = $middleware(Psr7Request::fromZend($request), Psr7Response::fromZend($response));
61+
} catch (\Exception $exception) {
62+
$event->setName(MvcEvent::EVENT_DISPATCH_ERROR);
63+
$event->setError($application::ERROR_EXCEPTION);
64+
$event->setController($middlewareName);
65+
$event->setControllerClass(get_class($middleware));
66+
$event->setParam('exception', $exception);
67+
68+
$events = $application->getEventManager();
69+
$results = $events->triggerEvent($event);
70+
$return = $results->last();
71+
if (! $return) {
72+
$return = $event->getResult();
73+
}
74+
}
75+
76+
if (! $return instanceof PsrResponseInterface) {
77+
$event->setResult($return);
78+
return $return;
79+
}
80+
$response = Psr7Response::toZend($return);
81+
$event->setResult($response);
82+
return $response;
83+
}
84+
85+
/**
86+
* Marshal a middleware not callable exception event
87+
*
88+
* @param string $type
89+
* @param string $middlewareName
90+
* @param MvcEvent $event
91+
* @param Application $application
92+
* @param \Exception $exception
93+
* @return mixed
94+
*/
95+
protected function marshalMiddlewareNotCallable(
96+
$type,
97+
$middlewareName,
98+
MvcEvent $event,
99+
Application $application,
100+
\Exception $exception = null
101+
) {
102+
$event->setName(MvcEvent::EVENT_DISPATCH_ERROR);
103+
$event->setError($type);
104+
$event->setController($middlewareName);
105+
$event->setControllerClass('Middleware not callable: ' . $middlewareName);
106+
if ($exception !== null) {
107+
$event->setParam('exception', $exception);
108+
}
109+
110+
$events = $application->getEventManager();
111+
$results = $events->triggerEvent($event);
112+
$return = $results->last();
113+
if (! $return) {
114+
$return = $event->getResult();
115+
}
116+
return $return;
117+
}
118+
}

src/Service/ServiceListenerFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class ServiceListenerFactory implements FactoryInterface
3737
*/
3838
protected $defaultServiceConfig = [
3939
'invokables' => [
40+
'MiddlewareListener' => 'Zend\Mvc\MiddlewareListener',
4041
'RouteListener' => 'Zend\Mvc\RouteListener',
4142
'SendResponseListener' => 'Zend\Mvc\SendResponseListener',
4243
'ViewJsonRenderer' => 'Zend\View\Renderer\JsonRenderer',

test/ApplicationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ public function bootstrapRegistersListenersProvider()
188188
return [
189189
'route' => ['RouteListener' , MvcEvent::EVENT_ROUTE , 'onRoute', false],
190190
'dispatch' => ['DispatchListener' , MvcEvent::EVENT_DISPATCH , 'onDispatch', false],
191+
'middleware' => ['MiddlewareListener' , MvcEvent::EVENT_DISPATCH , 'onDispatch', false],
191192
'send_response' => ['SendResponseListener' , MvcEvent::EVENT_FINISH , 'sendResponse', false],
192193
'view_manager' => ['ViewManager' , MvcEvent::EVENT_BOOTSTRAP , 'onBootstrap', false],
193194
'http_method' => ['HttpMethodListener' , MvcEvent::EVENT_ROUTE , 'onRoute', false],

test/DispatchListenerTest.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,6 @@
2222

2323
class DispatchListenerTest extends TestCase
2424
{
25-
public function setUp()
26-
{
27-
}
28-
29-
public function setupPathController()
30-
{
31-
$request = $this->serviceManager->get('Request');
32-
$request->setUri('http://example.local/path');
33-
34-
$router = $this->serviceManager->get('HttpRouter');
35-
$route = Router\Http\Literal::factory([
36-
'route' => '/path',
37-
'defaults' => [
38-
'controller' => 'path',
39-
],
40-
]);
41-
$router->addRoute('path', $route);
42-
$this->application->bootstrap();
43-
}
44-
4525
public function createMvcEvent($controllerMatched)
4626
{
4727
$response = new Response();

test/MiddlewareListenerTest.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace ZendTest\Mvc;
11+
12+
use Interop\Container\ContainerInterface;
13+
use PHPUnit_Framework_TestCase as TestCase;
14+
use Psr\Http\Message\ResponseInterface;
15+
use Psr\Http\Message\ServerRequestInterface;
16+
use Zend\EventManager\EventManager;
17+
use Zend\Http\Request;
18+
use Zend\Http\Response;
19+
use Zend\Mvc\Application;
20+
use Zend\Mvc\MiddlewareListener;
21+
use Zend\Mvc\MvcEvent;
22+
use Zend\Mvc\Router\RouteMatch;
23+
24+
class MiddlewareListenerTest extends TestCase
25+
{
26+
/**
27+
* Create an MvcEvent, populated with everything it needs.
28+
*
29+
* @param string $middlewareMatched Middleware service matched by routing
30+
* @param mixed $middleware Value to return for middleware service
31+
* @return MvcEven
32+
*/
33+
public function createMvcEvent($middlewareMatched, $middleware = null)
34+
{
35+
$response = new Response();
36+
$routeMatch = $this->prophesize(RouteMatch::class);
37+
$routeMatch->getParam('middleware', false)->willReturn($middlewareMatched);
38+
39+
$eventManager = new EventManager();
40+
41+
$serviceManager = $this->prophesize(ContainerInterface::class);
42+
$serviceManager->has($middlewareMatched)->willReturn(true);
43+
$serviceManager->get($middlewareMatched)->willReturn($middleware);
44+
45+
$application = $this->prophesize(Application::class);
46+
$application->getEventManager()->willReturn($eventManager);
47+
$application->getServiceManager()->will(function () use ($serviceManager) {
48+
return $serviceManager->reveal();
49+
});
50+
$application->getResponse()->willReturn($response);
51+
52+
$event = new MvcEvent();
53+
$event->setRequest(new Request());
54+
$event->setResponse($response);
55+
$event->setApplication($application->reveal());
56+
$event->setRouteMatch($routeMatch->reveal());
57+
58+
return $event;
59+
}
60+
61+
public function testSuccessfullyDispatchesMiddleware()
62+
{
63+
$event = $this->createMvcEvent('path', function ($request, $response) {
64+
$this->assertInstanceOf(ServerRequestInterface::class, $request);
65+
$this->assertInstanceOf(ResponseInterface::class, $response);
66+
$response->getBody()->write('Test!');
67+
return $response;
68+
});
69+
$application = $event->getApplication();
70+
71+
$application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) {
72+
$this->fail(sprintf('dispatch.error triggered when it should not be: %s', var_export($e->getError(), 1)));
73+
});
74+
75+
$listener = new MiddlewareListener();
76+
$return = $listener->onDispatch($event);
77+
$this->assertInstanceOf(Response::class, $return);
78+
79+
$this->assertInstanceOf('Zend\Http\Response', $return);
80+
$this->assertSame(200, $return->getStatusCode());
81+
$this->assertEquals('Test!', $return->getBody());
82+
}
83+
84+
public function testTriggersErrorForUncallableMiddleware()
85+
{
86+
$event = $this->createMvcEvent('path');
87+
$application = $event->getApplication();
88+
89+
$application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) {
90+
$this->assertEquals(Application::ERROR_MIDDLEWARE_CANNOT_DISPATCH, $e->getError());
91+
$this->assertEquals('path', $e->getController());
92+
return 'FAILED';
93+
});
94+
95+
$listener = new MiddlewareListener();
96+
$return = $listener->onDispatch($event);
97+
$this->assertEquals('FAILED', $return);
98+
}
99+
100+
public function testTriggersErrorForExceptionRaisedInMiddleware()
101+
{
102+
$exception = new \Exception();
103+
$event = $this->createMvcEvent('path', function ($request, $response) use ($exception) {
104+
throw $exception;
105+
});
106+
107+
$application = $event->getApplication();
108+
$application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) use ($exception) {
109+
$this->assertEquals(Application::ERROR_EXCEPTION, $e->getError());
110+
$this->assertSame($exception, $e->getParam('exception'));
111+
return 'FAILED';
112+
});
113+
114+
$listener = new MiddlewareListener();
115+
$return = $listener->onDispatch($event);
116+
$this->assertEquals('FAILED', $return);
117+
}
118+
}

0 commit comments

Comments
 (0)