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

Commit afb3eca

Browse files
committed
Merge branch 'weierophinney-hotfix/277'
2 parents e4ecc41 + 59c76c9 commit afb3eca

File tree

4 files changed

+253
-1
lines changed

4 files changed

+253
-1
lines changed

src/ErrorMiddlewarePipe.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @see https://github.com/zendframework/zend-expressive for the canonical source repository
6+
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
8+
*/
9+
10+
namespace Zend\Expressive;
11+
12+
use Psr\Http\Message\ServerRequestInterface as Request;
13+
use Psr\Http\Message\ResponseInterface as Response;
14+
use ReflectionMethod;
15+
use ReflectionProperty;
16+
use Zend\Stratigility\FinalHandler;
17+
use Zend\Stratigility\MiddlewarePipe;
18+
use Zend\Stratigility\Next;
19+
20+
/**
21+
* MiddlewarePipe implementation that acts as error middleware.
22+
*
23+
* Normal MiddlewarePipe implementations implement Zend\Stratigility\MiddlewareInterface,
24+
* which can be consumed as normal middleware, but not as error middleware, as
25+
* the signature for error middleware differs.
26+
*
27+
* This class wraps a MiddlewarePipe, and consumes its internal pipeline
28+
* within a functor signature that works for error middleware.
29+
*
30+
* It is not implemented as an extension of MiddlewarePipe, as that class
31+
* implements the MiddlewareInterface, which prevents its use as error
32+
* middleware.
33+
*/
34+
class ErrorMiddlewarePipe
35+
{
36+
/**
37+
* @var MiddlewarePipe
38+
*/
39+
private $pipeline;
40+
41+
/**
42+
* @param MiddlewarePipe $pipe
43+
*/
44+
public function __construct(MiddlewarePipe $pipeline)
45+
{
46+
$this->pipeline = $pipeline;
47+
}
48+
49+
/**
50+
* Handle an error request.
51+
*
52+
* This is essentially a version of the MiddlewarePipe that acts as a pipeline
53+
* for solely error middleware; it's primary use case is to allow configuring
54+
* arrays of error middleware as a single pipeline.
55+
*
56+
* Operation is identical to MiddlewarePipe, with the single exception that
57+
* $next is called with the $error argument.
58+
*
59+
* @param mixed $error
60+
* @param Request $request
61+
* @param Response $response
62+
* @param callable $out
63+
* @return Response
64+
*/
65+
public function __invoke($error, Request $request, Response $response, callable $out = null)
66+
{
67+
// Decorate instances with Stratigility decorators; required to work
68+
// with Next implementation.
69+
$request = $this->decorateRequest($request);
70+
$response = $this->decorateResponse($response);
71+
72+
$pipeline = $this->getInternalPipeline();
73+
$done = $out ?: new FinalHandler([], $response);
74+
$next = new Next($pipeline, $done);
75+
$result = $next($request, $response, $error);
76+
77+
return ($result instanceof Response ? $result : $response);
78+
}
79+
80+
/**
81+
* Retrieve the internal pipeline from the composed MiddlewarePipe.
82+
*
83+
* Uses reflection to retrieve the internal pipeline from the composed
84+
* MiddlewarePipe, in order to allow using it to create a Next instance.
85+
*
86+
* @return \SplQueue
87+
*/
88+
private function getInternalPipeline()
89+
{
90+
$r = new ReflectionProperty($this->pipeline, 'pipeline');
91+
$r->setAccessible(true);
92+
return $r->getValue($this->pipeline);
93+
}
94+
95+
/**
96+
* Decorate the request with the Stratigility decorator.
97+
*
98+
* Proxies to the composed MiddlewarePipe's equivalent method.
99+
*
100+
* @param Request $request
101+
* @return \Zend\Stratigility\Http\Request
102+
*/
103+
private function decorateRequest(Request $request)
104+
{
105+
$r = new ReflectionMethod($this->pipeline, 'decorateRequest');
106+
$r->setAccessible(true);
107+
return $r->invoke($this->pipeline, $request);
108+
}
109+
110+
/**
111+
* Decorate the response with the Stratigility decorator.
112+
*
113+
* Proxies to the composed MiddlewarePipe's equivalent method.
114+
*
115+
* @param Response $response
116+
* @return \Zend\Stratigility\Http\Response
117+
*/
118+
private function decorateResponse(Response $response)
119+
{
120+
$r = new ReflectionMethod($this->pipeline, 'decorateResponse');
121+
$r->setAccessible(true);
122+
return $r->invoke($this->pipeline, $response);
123+
}
124+
}

src/MarshalMiddlewareTrait.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ private function prepareMiddleware($middleware, ContainerInterface $container =
8787
* @param null|ContainerInterface $container
8888
* @param bool $forError Whether or not the middleware pipe generated is
8989
* intended to be populated with error middleware; defaults to false.
90-
* @return MiddlewarePipe
90+
* @return MiddlewarePipe|ErrorMiddlewarePipe When $forError is true,
91+
* returns an ErrorMiddlewarePipe.
9192
* @throws Exception\InvalidMiddlewareException for any invalid middleware items.
9293
*/
9394
private function marshalMiddlewarePipe(array $middlewares, ContainerInterface $container = null, $forError = false)
@@ -100,6 +101,10 @@ private function marshalMiddlewarePipe(array $middlewares, ContainerInterface $c
100101
);
101102
}
102103

104+
if ($forError) {
105+
return new ErrorMiddlewarePipe($middlewarePipe);
106+
}
107+
103108
return $middlewarePipe;
104109
}
105110

test/Container/ApplicationFactoryTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit_Framework_TestCase as TestCase;
1515
use Prophecy\Prophecy\ObjectProphecy;
1616
use ReflectionFunction;
17+
use ReflectionMethod;
1718
use ReflectionProperty;
1819
use SplQueue;
1920
use Zend\Diactoros\Response\EmitterInterface;
@@ -22,10 +23,12 @@
2223
use Zend\Expressive\Container\ApplicationFactory;
2324
use Zend\Expressive\Container\Exception as ContainerException;
2425
use Zend\Expressive\Emitter\EmitterStack;
26+
use Zend\Expressive\ErrorMiddlewarePipe;
2527
use Zend\Expressive\Exception\InvalidMiddlewareException;
2628
use Zend\Expressive\Router\FastRouteRouter;
2729
use Zend\Expressive\Router\Route;
2830
use Zend\Expressive\Router\RouterInterface;
31+
use Zend\Stratigility\ErrorMiddlewareInterface;
2932
use Zend\Stratigility\MiddlewarePipe;
3033
use Zend\Stratigility\Route as StratigilityRoute;
3134
use ZendTest\Expressive\ContainerTrait;
@@ -1253,4 +1256,45 @@ public function testRoutingAndDispatchMiddlewareCanBeComposedWithinArrayStandard
12531256
$this->assertContains($middleware, $innerPipeline);
12541257
}
12551258
}
1259+
1260+
public function testProperlyRegistersNestedErrorMiddlewareAsLazyErrorMiddleware()
1261+
{
1262+
$config = ['middleware_pipeline' => [
1263+
'error' => [
1264+
'middleware' => [
1265+
'FooError',
1266+
],
1267+
'error' => true,
1268+
'priority' => -10000,
1269+
],
1270+
]];
1271+
1272+
$this->injectServiceInContainer($this->container, 'config', $config);
1273+
$fooError = $this->prophesize(ErrorMiddlewareInterface::class)->reveal();
1274+
$this->injectServiceInContainer($this->container, 'FooError', $fooError);
1275+
1276+
$app = $this->factory->__invoke($this->container->reveal());
1277+
1278+
$r = new ReflectionProperty($app, 'pipeline');
1279+
$r->setAccessible(true);
1280+
$pipeline = $r->getValue($app);
1281+
1282+
$nestedPipeline = $pipeline->dequeue()->handler;
1283+
1284+
$this->assertInstanceOf(ErrorMiddlewarePipe::class, $nestedPipeline);
1285+
1286+
$r = new ReflectionProperty($nestedPipeline, 'pipeline');
1287+
$r->setAccessible(true);
1288+
$internalPipeline = $r->getValue($nestedPipeline);
1289+
$this->assertInstanceOf(MiddlewarePipe::class, $internalPipeline);
1290+
1291+
$r = new ReflectionProperty($internalPipeline, 'pipeline');
1292+
$r->setAccessible(true);
1293+
$middleware = $r->getValue($internalPipeline)->dequeue()->handler;
1294+
1295+
$this->assertInstanceOf(Closure::class, $middleware);
1296+
$r = new ReflectionFunction($middleware);
1297+
$this->assertTrue($r->isClosure(), 'Configured middleware is not the expected lazy-middleware closure');
1298+
$this->assertEquals(4, $r->getNumberOfParameters(), 'Configured middleware is not error middleware');
1299+
}
12561300
}

test/ErrorMiddlewarePipeTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @see http://github.com/zendframework/zend-expressive for the canonical source repository
6+
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
8+
*/
9+
10+
namespace ZendTest\Expressive;
11+
12+
use PHPUnit_Framework_TestCase as TestCase;
13+
use Psr\Http\Message\ServerRequestInterface as Request;
14+
use Psr\Http\Message\ResponseInterface as Response;
15+
use Psr\Http\Message\UriInterface as Uri;
16+
use Zend\Expressive\ErrorMiddlewarePipe;
17+
use Zend\Stratigility\Http\Response as StratigilityResponse;
18+
use Zend\Stratigility\MiddlewarePipe;
19+
20+
class ErrorMiddlewarePipeTest extends TestCase
21+
{
22+
public function setUp()
23+
{
24+
$this->internalPipe = new MiddlewarePipe();
25+
$this->errorPipe = new ErrorMiddlewarePipe($this->internalPipe);
26+
}
27+
28+
public function testWillDispatchErrorMiddlewareComposedInInternalPipeline()
29+
{
30+
$error = (object) ['error' => true];
31+
$triggered = (object) [
32+
'first' => false,
33+
'second' => false,
34+
'third' => false,
35+
];
36+
37+
$first = function ($err, $request, $response, $next) use ($error, $triggered) {
38+
$this->assertSame($error, $err);
39+
$triggered->first = true;
40+
return $next($request, $response, $err);
41+
};
42+
$second = function ($request, $response, $next) use ($triggered) {
43+
$triggered->second = true;
44+
return $next($request, $response);
45+
};
46+
$third = function ($err, $request, $response, $next) use ($error, $triggered) {
47+
$this->assertSame($error, $err);
48+
$triggered->third = true;
49+
return $response;
50+
};
51+
52+
$this->internalPipe->pipe($first);
53+
$this->internalPipe->pipe($second);
54+
$this->internalPipe->pipe($third);
55+
56+
$uri = $this->prophesize(Uri::class);
57+
$uri->getPath()->willReturn('/');
58+
$request = $this->prophesize(Request::class);
59+
$request->getUri()->willReturn($uri->reveal());
60+
61+
// The following is required due to Stratigility decorating requests:
62+
$request->withAttribute('originalUri', $uri->reveal())->will(function () use ($request) {
63+
return $request->reveal();
64+
});
65+
66+
$response = $this->prophesize(Response::class);
67+
68+
$final = function ($request, $response, $err = null) {
69+
$this->fail('Final handler should not be triggered');
70+
};
71+
72+
$result = $this->errorPipe->__invoke($error, $request->reveal(), $response->reveal(), $final);
73+
$this->assertInstanceOf(StratigilityResponse::class, $result);
74+
$this->assertSame($response->reveal(), $result->getOriginalResponse());
75+
$this->assertTrue($triggered->first);
76+
$this->assertFalse($triggered->second);
77+
$this->assertTrue($triggered->third);
78+
}
79+
}

0 commit comments

Comments
 (0)