This repository has been archived by the owner on Jan 21, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merging develop to master in preparation for 2.3.0 release
- Loading branch information
Showing
5 changed files
with
372 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-router for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-router/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Expressive\Router; | ||
|
||
use Interop\Http\Middleware\ServerMiddlewareInterface as LegacyLegacyMiddlewareInterface; | ||
use Interop\Http\ServerMiddleware\MiddlewareInterface as LegacyMiddlewareInterface; | ||
use Interop\Http\Server\MiddlewareInterface as InteropMiddlewareInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
/** | ||
* Default dispatch middleware. | ||
* | ||
* Checks for a composed route result in the request. If none is provided, | ||
* delegates to the next middleware. | ||
* | ||
* Otherwise, it pulls the middleware from the route result. If the middleware | ||
* is not http-interop middleware, it raises an exception. This means that | ||
* this middleware is incompatible with routes that store non-http-interop | ||
* middleware instances! Make certain you only provide middleware instances | ||
* to your router when using this middleware. | ||
*/ | ||
class DispatchMiddleware implements MiddlewareInterface | ||
{ | ||
/** | ||
* @param ServerRequestInterface $request | ||
* @param HandlerInterface $handler | ||
* @return ResponseInterface | ||
*/ | ||
public function process(ServerRequestInterface $request, HandlerInterface $handler) | ||
{ | ||
$routeResult = $request->getAttribute(RouteResult::class, false); | ||
if (! $routeResult) { | ||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
|
||
$middleware = $routeResult->getMatchedMiddleware(); | ||
|
||
if (! $middleware instanceof LegacyLegacyMiddlewareInterface | ||
&& ! $middleware instanceof LegacyMiddlewareInterface | ||
&& ! $middleware instanceof InteropMiddlewareInterface | ||
) { | ||
throw new Exception\RuntimeException(sprintf( | ||
'Unknown middleware type stored in route; %s expects an http-interop' | ||
. ' middleware instance; received %s', | ||
__CLASS__, | ||
is_object($middleware) ? get_class($middleware) : gettype($middleware) | ||
)); | ||
} | ||
|
||
return $middleware->process($request, $handler); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-router for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-router/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Expressive\Router; | ||
|
||
use Fig\Http\Message\StatusCodeInterface as StatusCode; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Zend\Expressive\Router\RouteResult; | ||
use Zend\Expressive\Router\RouterInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
/** | ||
* Default routing middleware. | ||
* | ||
* Uses the composed router to match against the incoming request. | ||
* | ||
* When routing failure occurs, if the failure is due to HTTP method, uses | ||
* the composed response prototype to generate a 405 response; otherwise, | ||
* it delegates to the next middleware. | ||
* | ||
* If routing succeeds, injects the route result into the request (under the | ||
* RouteResult class name), as well as any matched parameters, before | ||
* delegating to the next middleware. | ||
*/ | ||
class RouteMiddleware implements MiddlewareInterface | ||
{ | ||
/** | ||
* Response prototype for 405 responses. | ||
* | ||
* @var ResponseInterface | ||
*/ | ||
private $responsePrototype; | ||
|
||
/** | ||
* @var RouterInterface | ||
*/ | ||
private $router; | ||
|
||
/** | ||
* @param RouterInterface $router | ||
* @param ResponseInterface $responsePrototype | ||
*/ | ||
public function __construct(RouterInterface $router, ResponseInterface $responsePrototype) | ||
{ | ||
$this->router = $router; | ||
$this->responsePrototype = $responsePrototype; | ||
} | ||
|
||
/** | ||
* @param ServerRequestInterface $request | ||
* @param HandlerInterface $handler | ||
* @return ResponseInterface | ||
*/ | ||
public function process(ServerRequestInterface $request, HandlerInterface $handler) | ||
{ | ||
$result = $this->router->match($request); | ||
|
||
if ($result->isFailure()) { | ||
if ($result->isMethodFailure()) { | ||
return $this->responsePrototype->withStatus(StatusCode::STATUS_METHOD_NOT_ALLOWED) | ||
->withHeader('Allow', implode(',', $result->getAllowedMethods())); | ||
} | ||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
|
||
// Inject the actual route result, as well as individual matched parameters. | ||
$request = $request->withAttribute(RouteResult::class, $result); | ||
foreach ($result->getMatchedParams() as $param => $value) { | ||
$request = $request->withAttribute($param, $value); | ||
} | ||
|
||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-router for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-router/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace ZendTest\Expressive\Router; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Prophecy\Prophecy\ObjectProphecy; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
use Zend\Expressive\Router\DispatchMiddleware; | ||
use Zend\Expressive\Router\Exception\RuntimeException; | ||
use Zend\Expressive\Router\Route; | ||
use Zend\Expressive\Router\RouteResult; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
class DispatchMiddlewareTest extends TestCase | ||
{ | ||
/** @var HandlerInterface|ObjectProphecy */ | ||
private $handler; | ||
|
||
/** @var DispatchMiddleware */ | ||
private $middleware; | ||
|
||
public function setUp() | ||
{ | ||
$this->request = $this->prophesize(ServerRequestInterface::class); | ||
$this->handler = $this->prophesize(HandlerInterface::class); | ||
$this->middleware = new DispatchMiddleware(); | ||
} | ||
|
||
public function testInvokesDelegateIfRequestDoesNotContainRouteResult() | ||
{ | ||
$expected = $this->prophesize(ResponseInterface::class)->reveal(); | ||
$this->request->getAttribute(RouteResult::class, false)->willReturn(false); | ||
$this->handler->{HANDLER_METHOD}($this->request->reveal())->willReturn($expected); | ||
|
||
$response = $this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
|
||
$this->assertSame($expected, $response); | ||
} | ||
|
||
public function testInvokesMatchedMiddlewareWhenRouteResult() | ||
{ | ||
$this->handler->{HANDLER_METHOD}()->shouldNotBeCalled(); | ||
|
||
$expected = $this->prophesize(ResponseInterface::class)->reveal(); | ||
$routedMiddleware = $this->prophesize(MiddlewareInterface::class); | ||
$routedMiddleware | ||
->process($this->request->reveal(), $this->handler->reveal()) | ||
->willReturn($expected); | ||
|
||
$routeResult = RouteResult::fromRoute(new Route('/', $routedMiddleware->reveal())); | ||
|
||
$this->request->getAttribute(RouteResult::class, false)->willReturn($routeResult); | ||
|
||
$response = $this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
|
||
$this->assertSame($expected, $response); | ||
} | ||
|
||
public function invalidMiddleware() | ||
{ | ||
return [ | ||
// @codingStandardsIgnoreStart | ||
// There are more types we could test, but Route has a number of tests | ||
// in place already, and these are the three it allows that the dispatch | ||
// middleware cannot allow. | ||
'string' => ['middleware'], | ||
'array' => [['middleware']], | ||
'callable' => [function () {}], | ||
// @codingStandardsIgnoreEnd | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider invalidMiddleware | ||
* @param mixed $middleware | ||
*/ | ||
public function testInvalidRoutedMiddlewareInRouteResultResultsInException($middleware) | ||
{ | ||
$this->handler->{HANDLER_METHOD}()->shouldNotBeCalled(); | ||
$routeResult = RouteResult::fromRoute(new Route('/', $middleware)); | ||
$this->request->getAttribute(RouteResult::class, false)->willReturn($routeResult); | ||
|
||
$this->expectException(RuntimeException::class); | ||
$this->expectExceptionMessage('expects an http-interop'); | ||
$this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-router for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-router/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace ZendTest\Expressive\Router; | ||
|
||
use Fig\Http\Message\StatusCodeInterface as StatusCode; | ||
use PHPUnit\Framework\TestCase; | ||
use Prophecy\Prophecy\ObjectProphecy; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
use Zend\Expressive\Router\Route; | ||
use Zend\Expressive\Router\RouteMiddleware; | ||
use Zend\Expressive\Router\RouteResult; | ||
use Zend\Expressive\Router\RouterInterface; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
class RouteMiddlewareTest extends TestCase | ||
{ | ||
/** @var RouterInterface|ObjectProphecy */ | ||
private $router; | ||
|
||
/** @var ResponseInterface|ObjectProphecy */ | ||
private $response; | ||
|
||
/** @var RouteMiddleware */ | ||
private $middleware; | ||
|
||
/** @var ServerRequestInterface|ObjectProphecy */ | ||
private $request; | ||
|
||
/** @var HandlerInterface|ObjectProphecy */ | ||
private $handler; | ||
|
||
public function setUp() | ||
{ | ||
$this->router = $this->prophesize(RouterInterface::class); | ||
$this->response = $this->prophesize(ResponseInterface::class); | ||
$this->middleware = new RouteMiddleware( | ||
$this->router->reveal(), | ||
$this->response->reveal() | ||
); | ||
|
||
$this->request = $this->prophesize(ServerRequestInterface::class); | ||
$this->handler = $this->prophesize(HandlerInterface::class); | ||
} | ||
|
||
public function testRoutingFailureDueToHttpMethodCallsNextWithNotAllowedResponseAndError() | ||
{ | ||
$result = RouteResult::fromRouteFailure(['GET', 'POST']); | ||
|
||
$this->router->match($this->request->reveal())->willReturn($result); | ||
$this->handler->{HANDLER_METHOD}()->shouldNotBeCalled(); | ||
$this->request->withAttribute()->shouldNotBeCalled(); | ||
$this->response->withStatus(StatusCode::STATUS_METHOD_NOT_ALLOWED)->will([$this->response, 'reveal']); | ||
$this->response->withHeader('Allow', 'GET,POST')->will([$this->response, 'reveal']); | ||
|
||
$response = $this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
$this->assertSame($response, $this->response->reveal()); | ||
} | ||
|
||
public function testGeneralRoutingFailureInvokesDelegateWithSameRequest() | ||
{ | ||
$result = RouteResult::fromRouteFailure(); | ||
|
||
$this->router->match($this->request->reveal())->willReturn($result); | ||
$this->response->withStatus()->shouldNotBeCalled(); | ||
$this->response->withHeader()->shouldNotBeCalled(); | ||
$this->request->withAttribute()->shouldNotBeCalled(); | ||
|
||
$expected = $this->prophesize(ResponseInterface::class)->reveal(); | ||
$this->handler->{HANDLER_METHOD}($this->request->reveal())->willReturn($expected); | ||
|
||
$response = $this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
$this->assertSame($expected, $response); | ||
} | ||
|
||
public function testRoutingSuccessDelegatesToNextAfterFirstInjectingRouteResultAndAttributesInRequest() | ||
{ | ||
$middleware = $this->prophesize(MiddlewareInterface::class)->reveal(); | ||
$parameters = ['foo' => 'bar', 'baz' => 'bat']; | ||
$result = RouteResult::fromRoute( | ||
new Route('/foo', $middleware), | ||
$parameters | ||
); | ||
|
||
$this->router->match($this->request->reveal())->willReturn($result); | ||
|
||
$this->request | ||
->withAttribute(RouteResult::class, $result) | ||
->will([$this->request, 'reveal']); | ||
foreach ($parameters as $key => $value) { | ||
$this->request->withAttribute($key, $value)->will([$this->request, 'reveal']); | ||
} | ||
|
||
$this->response->withStatus()->shouldNotBeCalled(); | ||
$this->response->withHeader()->shouldNotBeCalled(); | ||
|
||
$expected = $this->prophesize(ResponseInterface::class)->reveal(); | ||
$this->handler | ||
->{HANDLER_METHOD}($this->request->reveal()) | ||
->willReturn($expected); | ||
|
||
$response = $this->middleware->process($this->request->reveal(), $this->handler->reveal()); | ||
$this->assertSame($expected, $response); | ||
} | ||
} |