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

Commit 486ff29

Browse files
committed
Merge branch 'feature/236' into develop
Close #236
2 parents 9935a57 + 47a1a11 commit 486ff29

8 files changed

+538
-46
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ All notable changes to this project will be documented in this file, in reverse
1212
it will be marshaled into a `Zend\Stratigility\MiddlewarePipe` instance, using
1313
the same rules as if you specified a single middleware.
1414

15+
- [#236](https://github.com/zendframework/zend-mvc/pull/236) adds the ability to
16+
attach dispatch listeners to middleware when using the `MiddlewareListener`.
17+
Attach shared events to the class identifier `Zend\Mvc\Controller\MiddlewareController`.
18+
This feature helps ensure that listeners that should run for every controller
19+
(e.g., authentication or authorization listeners) will run even for
20+
middleware.
21+
1522
### Deprecated
1623

1724
- Nothing.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-mvc for the canonical source repository
4+
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-mvc/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
namespace Zend\Mvc\Controller;
9+
10+
use Psr\Http\Message\ResponseInterface;
11+
use Psr\Http\Message\ServerRequestInterface;
12+
use Zend\EventManager\EventManagerInterface;
13+
use Zend\Http\Request;
14+
use Zend\Mvc\Exception\ReachedFinalHandlerException;
15+
use Zend\Mvc\Exception\RuntimeException;
16+
use Zend\Mvc\MvcEvent;
17+
use Zend\Psr7Bridge\Psr7ServerRequest;
18+
use Zend\Router\RouteMatch;
19+
use Zend\Stratigility\Delegate\CallableDelegateDecorator;
20+
use Zend\Stratigility\MiddlewarePipe;
21+
22+
/**
23+
* @internal don't use this in your codebase, or else @ocramius will hunt you
24+
* down. This is just an internal hack to make middleware trigger
25+
* 'dispatch' events attached to the DispatchableInterface identifier.
26+
*
27+
* Specifically, it will receive a @see MiddlewarePipe and a
28+
* @see ResponseInterface prototype, and then dispatch the pipe whilst still
29+
* behaving like a normal controller. That is needed for any events
30+
* attached to the @see \Zend\Stdlib\DispatchableInterface identifier to
31+
* reach their listeners on any attached
32+
* @see \Zend\EventManager\SharedEventManagerInterface
33+
*/
34+
final class MiddlewareController extends AbstractController
35+
{
36+
/**
37+
* @var MiddlewarePipe
38+
*/
39+
private $pipe;
40+
41+
/**
42+
* @var ResponseInterface
43+
*/
44+
private $responsePrototype;
45+
46+
public function __construct(
47+
MiddlewarePipe $pipe,
48+
ResponseInterface $responsePrototype,
49+
EventManagerInterface $eventManager,
50+
MvcEvent $event
51+
) {
52+
$this->eventIdentifier = __CLASS__;
53+
$this->pipe = $pipe;
54+
$this->responsePrototype = $responsePrototype;
55+
56+
$this->setEventManager($eventManager);
57+
$this->setEvent($event);
58+
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*
63+
* @throws RuntimeException
64+
*/
65+
public function onDispatch(MvcEvent $e)
66+
{
67+
$routeMatch = $e->getRouteMatch();
68+
$psr7Request = $this->populateRequestParametersFromRoute(
69+
$this->loadRequest()->withAttribute(RouteMatch::class, $routeMatch),
70+
$routeMatch
71+
);
72+
73+
$result = $this->pipe->process($psr7Request, new CallableDelegateDecorator(
74+
function () {
75+
throw ReachedFinalHandlerException::create();
76+
},
77+
$this->responsePrototype
78+
));
79+
80+
$e->setResult($result);
81+
82+
return $result;
83+
}
84+
85+
/**
86+
* @return \Zend\Diactoros\ServerRequest
87+
*
88+
* @throws RuntimeException
89+
*/
90+
private function loadRequest()
91+
{
92+
$request = $this->request;
93+
94+
if (! $request instanceof Request) {
95+
throw new RuntimeException(sprintf(
96+
'Expected request to be a %s, %s given',
97+
Request::class,
98+
get_class($request)
99+
));
100+
}
101+
102+
return Psr7ServerRequest::fromZend($request);
103+
}
104+
105+
/**
106+
* @param ServerRequestInterface $request
107+
* @param RouteMatch|null $routeMatch
108+
*
109+
* @return ServerRequestInterface
110+
*/
111+
private function populateRequestParametersFromRoute(ServerRequestInterface $request, RouteMatch $routeMatch = null)
112+
{
113+
if (! $routeMatch) {
114+
return $request;
115+
}
116+
117+
foreach ($routeMatch->getParams() as $key => $value) {
118+
$request = $request->withAttribute($key, $value);
119+
}
120+
121+
return $request;
122+
}
123+
}

src/DispatchListener.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?php
22
/**
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
3+
* @see https://github.com/zendframework/zend-mvc for the canonical source repository
4+
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-mvc/blob/master/LICENSE.md New BSD License
86
*/
97

108
namespace Zend\Mvc;
@@ -76,6 +74,10 @@ public function attach(EventManagerInterface $events, $priority = 1)
7674
*/
7775
public function onDispatch(MvcEvent $e)
7876
{
77+
if (null !== $e->getResult()) {
78+
return;
79+
}
80+
7981
$routeMatch = $e->getRouteMatch();
8082
$controllerName = $routeMatch instanceof RouteMatch
8183
? $routeMatch->getParam('controller', 'not-found')

src/MiddlewareListener.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?php
22
/**
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
3+
* @see https://github.com/zendframework/zend-mvc for the canonical source repository
4+
* @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-mvc/blob/master/LICENSE.md New BSD License
86
*/
97

108
namespace Zend\Mvc;
@@ -18,7 +16,7 @@
1816
use Zend\EventManager\EventManagerInterface;
1917
use Zend\Mvc\Exception\InvalidMiddlewareException;
2018
use Zend\Mvc\Exception\ReachedFinalHandlerException;
21-
use Zend\Psr7Bridge\Psr7ServerRequest as Psr7Request;
19+
use Zend\Mvc\Controller\MiddlewareController;
2220
use Zend\Psr7Bridge\Psr7Response;
2321
use Zend\Router\RouteMatch;
2422
use Zend\Stratigility\Delegate\CallableDelegateDecorator;
@@ -45,6 +43,10 @@ public function attach(EventManagerInterface $events, $priority = 1)
4543
*/
4644
public function onDispatch(MvcEvent $event)
4745
{
46+
if (null !== $event->getResult()) {
47+
return;
48+
}
49+
4850
$routeMatch = $event->getRouteMatch();
4951
$middleware = $routeMatch->getParam('middleware', false);
5052
if (false === $middleware) {
@@ -78,16 +80,12 @@ public function onDispatch(MvcEvent $event)
7880

7981
$caughtException = null;
8082
try {
81-
$psr7Request = Psr7Request::fromZend($request)->withAttribute(RouteMatch::class, $routeMatch);
82-
foreach ($routeMatch->getParams() as $key => $value) {
83-
$psr7Request = $psr7Request->withAttribute($key, $value);
84-
}
85-
$return = $pipe->process($psr7Request, new CallableDelegateDecorator(
86-
function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
87-
throw ReachedFinalHandlerException::create();
88-
},
89-
$psr7ResponsePrototype
90-
));
83+
$return = (new MiddlewareController(
84+
$pipe,
85+
$psr7ResponsePrototype,
86+
$application->getServiceManager()->get('EventManager'),
87+
$event
88+
))->dispatch($request, $response);
9189
} catch (\Throwable $ex) {
9290
$caughtException = $ex;
9391
} catch (\Exception $ex) { // @TODO clean up once PHP 7 requirement is enforced
@@ -107,6 +105,8 @@ function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
107105
}
108106
}
109107

108+
$event->setError('');
109+
110110
if (! $return instanceof PsrResponseInterface) {
111111
$event->setResult($return);
112112
return $return;
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-mvc for the canonical source repository
4+
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-mvc/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
namespace ZendTest\Mvc\Controller;
9+
10+
use PHPUnit\Framework\TestCase;
11+
use Psr\Http\Message\ResponseInterface;
12+
use Zend\EventManager\EventManager;
13+
use Zend\EventManager\EventManagerInterface;
14+
use Zend\Http\Request;
15+
use Zend\Http\Response;
16+
use Zend\Mvc\Controller\AbstractController;
17+
use Zend\Mvc\Controller\MiddlewareController;
18+
use Zend\Mvc\Exception\RuntimeException;
19+
use Zend\Mvc\MvcEvent;
20+
use Zend\Stdlib\DispatchableInterface;
21+
use Zend\Stdlib\RequestInterface;
22+
use Zend\Stratigility\MiddlewarePipe;
23+
24+
/**
25+
* @covers \Zend\Mvc\Controller\MiddlewareController
26+
*/
27+
class MiddlewareControllerTest extends TestCase
28+
{
29+
/**
30+
* @var MiddlewarePipe|\PHPUnit_Framework_MockObject_MockObject
31+
*/
32+
private $pipe;
33+
34+
/**
35+
* @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject
36+
*/
37+
private $responsePrototype;
38+
39+
/**
40+
* @var EventManagerInterface
41+
*/
42+
private $eventManager;
43+
44+
/**
45+
* @var AbstractController|\PHPUnit_Framework_MockObject_MockObject
46+
*/
47+
private $controller;
48+
49+
/**
50+
* @var MvcEvent
51+
*/
52+
private $event;
53+
54+
/**
55+
* {@inheritDoc}
56+
*/
57+
protected function setUp()
58+
{
59+
$this->pipe = $this->createMock(MiddlewarePipe::class);
60+
$this->responsePrototype = $this->createMock(ResponseInterface::class);
61+
$this->eventManager = $this->createMock(EventManagerInterface::class);
62+
$this->event = new MvcEvent();
63+
$this->eventManager = new EventManager();
64+
65+
$this->controller = new MiddlewareController(
66+
$this->pipe,
67+
$this->responsePrototype,
68+
$this->eventManager,
69+
$this->event
70+
);
71+
}
72+
73+
public function testWillAssignCorrectEventManagerIdentifiers()
74+
{
75+
$identifiers = $this->eventManager->getIdentifiers();
76+
77+
self::assertContains(MiddlewareController::class, $identifiers);
78+
self::assertContains(AbstractController::class, $identifiers);
79+
self::assertContains(DispatchableInterface::class, $identifiers);
80+
}
81+
82+
public function testWillDispatchARequestAndResponseWithAGivenPipe()
83+
{
84+
$request = new Request();
85+
$response = new Response();
86+
$result = $this->createMock(ResponseInterface::class);
87+
/* @var $dispatchListener callable|\PHPUnit_Framework_MockObject_MockObject */
88+
$dispatchListener = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
89+
90+
$this->eventManager->attach(MvcEvent::EVENT_DISPATCH, $dispatchListener, 100);
91+
$this->eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, function () {
92+
self::fail('No dispatch error expected');
93+
}, 100);
94+
95+
$dispatchListener
96+
->expects(self::once())
97+
->method('__invoke')
98+
->with(self::callback(function (MvcEvent $event) use ($request, $response) {
99+
self::assertSame($this->event, $event);
100+
self::assertSame(MvcEvent::EVENT_DISPATCH, $event->getName());
101+
self::assertSame($this->controller, $event->getTarget());
102+
self::assertSame($request, $event->getRequest());
103+
self::assertSame($response, $event->getResponse());
104+
105+
return true;
106+
}));
107+
108+
$this->pipe->expects(self::once())->method('process')->willReturn($result);
109+
110+
$controllerResult = $this->controller->dispatch($request, $response);
111+
112+
self::assertSame($result, $controllerResult);
113+
self::assertSame($result, $this->event->getResult());
114+
}
115+
116+
public function testWillRefuseDispatchingInvalidRequestTypes()
117+
{
118+
/* @var $request RequestInterface */
119+
$request = $this->createMock(RequestInterface::class);
120+
$response = new Response();
121+
/* @var $dispatchListener callable|\PHPUnit_Framework_MockObject_MockObject */
122+
$dispatchListener = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
123+
124+
$this->eventManager->attach(MvcEvent::EVENT_DISPATCH, $dispatchListener, 100);
125+
126+
$dispatchListener
127+
->expects(self::once())
128+
->method('__invoke')
129+
->with(self::callback(function (MvcEvent $event) use ($request, $response) {
130+
self::assertSame($this->event, $event);
131+
self::assertSame(MvcEvent::EVENT_DISPATCH, $event->getName());
132+
self::assertSame($this->controller, $event->getTarget());
133+
self::assertSame($request, $event->getRequest());
134+
self::assertSame($response, $event->getResponse());
135+
136+
return true;
137+
}));
138+
139+
$this->pipe->expects(self::never())->method('process');
140+
141+
$this->expectException(RuntimeException::class);
142+
143+
$this->controller->dispatch($request, $response);
144+
}
145+
}

0 commit comments

Comments
 (0)