forked from zendframework/zend-stratigility
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Backports middleware decorators from zendframework#134
This patch backports the following classes from the release-3.0.0 development series: - `Zend\Stratigility\Middleware\CallableMiddlewareDecorator`, for decorating middleware that follows the PSR-15 signature. - `Zend\Stratigility\Middleware\DoublePassMiddlewareDecorator`, for decorating callable middleware that follows the double pass middleware signature. - `Zend\Stratigility\Middleware\PathMiddlewareDecorator`, for providing path-segregated middleware. This required also extracting the class `Zend\Stratigility\Middleware\PathRequestHandlerDecorator`, which is used internally in order to decorate the request handler passed to middleware it decorates. All classes and their were updated to adapt to the http-middleware-compatibility shims.
- Loading branch information
1 parent
a5b6608
commit 032bdbc
Showing
12 changed files
with
1,085 additions
and
247 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
Large diffs are not rendered by default.
Oops, something went wrong.
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,16 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Stratigility\Exception; | ||
|
||
/** | ||
* @deprecated since 2.2.0; to be removed in 3.0.0. The need for this class | ||
* disappears with strict types in PHP 7. | ||
*/ | ||
class InvalidArgumentException extends \InvalidArgumentException | ||
{ | ||
} |
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,28 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Stratigility\Exception; | ||
|
||
use RuntimeException; | ||
|
||
class PathOutOfSyncException extends RuntimeException | ||
{ | ||
/** | ||
* @param string $pathPrefix | ||
* @param string $path | ||
* @return self | ||
*/ | ||
public static function forPath($pathPrefix, $path) | ||
{ | ||
return new self(sprintf( | ||
'Layer path "%s" and request path "%s" are out of sync; cannot dispatch' | ||
. ' middleware layer', | ||
$pathPrefix, | ||
$path | ||
)); | ||
} | ||
} |
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,58 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository | ||
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Stratigility\Middleware; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface as RequestHandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
use Zend\Stratigility\Exception; | ||
|
||
/** | ||
* Decorate callable middleware as PSR-15 middleware. | ||
* | ||
* Decorates middleware with the following signature: | ||
* | ||
* <code> | ||
* function ( | ||
* ServerRequestInterface $request, | ||
* RequestHandlerInterface $handler | ||
* ) : ResponseInterface | ||
* </code> | ||
* | ||
* such that it will operate as PSR-15 middleware. | ||
* | ||
* Neither the arguments nor the return value need be typehinted; however, if | ||
* the signature is incompatible, a PHP Error will likely be thrown. | ||
*/ | ||
class CallableMiddlewareDecorator implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var callable | ||
*/ | ||
private $middleware; | ||
|
||
public function __construct(callable $middleware) | ||
{ | ||
$this->middleware = $middleware; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* @throws Exception\MissingResponseException if the decorated middleware | ||
* fails to produce a response. | ||
*/ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) | ||
{ | ||
$response = ($this->middleware)($request, $handler); | ||
if (! $response instanceof ResponseInterface) { | ||
throw Exception\MissingResponseException::forCallableMiddleware($this->middleware); | ||
} | ||
return $response; | ||
} | ||
} |
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,93 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository | ||
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Stratigility\Middleware; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface as RequestHandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
use Zend\Diactoros\Response; | ||
use Zend\Stratigility\Exception; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
/** | ||
* Decorate double-pass middleware as PSR-15 middleware. | ||
* | ||
* Decorates middleware with the following signature: | ||
* | ||
* <code> | ||
* function ( | ||
* ServerRequestInterface $request, | ||
* ResponseInterface $response, | ||
* callable $next | ||
* ) : ResponseInterface | ||
* </code> | ||
* | ||
* such that it will operate as PSR-15 middleware. | ||
* | ||
* Neither the arguments nor the return value need be typehinted; however, if | ||
* the signature is incompatible, a PHP Error will likely be thrown. | ||
*/ | ||
class DoublePassMiddlewareDecorator implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var callable | ||
*/ | ||
private $middleware; | ||
|
||
/** | ||
* @var ResponseInterface | ||
*/ | ||
private $responsePrototype; | ||
|
||
/** | ||
* @throws Exception\MissingResponsePrototypeException if no response | ||
* prototype is present, and zend-diactoros is not installed. | ||
*/ | ||
public function __construct(callable $middleware, ResponseInterface $responsePrototype = null) | ||
{ | ||
$this->middleware = $middleware; | ||
|
||
if (! $responsePrototype && ! class_exists(Response::class)) { | ||
throw Exception\MissingResponsePrototypeException::create(); | ||
} | ||
|
||
$this->responsePrototype = $responsePrototype ?: new Response(); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* @throws Exception\MissingResponseException if the decorated middleware | ||
* fails to produce a response. | ||
*/ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) | ||
{ | ||
$response = ($this->middleware)( | ||
$request, | ||
$this->responsePrototype, | ||
$this->decorateHandler($handler) | ||
); | ||
|
||
if (! $response instanceof ResponseInterface) { | ||
throw Exception\MissingResponseException::forCallableMiddleware($this->middleware); | ||
} | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* @return callable | ||
*/ | ||
private function decorateHandler(RequestHandlerInterface $handler) | ||
{ | ||
return function ($request, $response) use ($handler) { | ||
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,144 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository | ||
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Stratigility\Middleware; | ||
|
||
use Psr\Http\Message\ServerRequestInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\HandlerInterface as RequestHandlerInterface; | ||
use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; | ||
use Zend\Stratigility\Exception; | ||
|
||
use const Webimpress\HttpMiddlewareCompatibility\HANDLER_METHOD; | ||
|
||
class PathMiddlewareDecorator implements MiddlewareInterface | ||
{ | ||
/** @var MiddlewareInterface */ | ||
private $middleware; | ||
|
||
/** @var string Path prefix under which the middleware is segregated. */ | ||
private $prefix; | ||
|
||
/** | ||
* @param string $prefix | ||
*/ | ||
public function __construct($prefix, MiddlewareInterface $middleware) | ||
{ | ||
if (! is_string($prefix)) { | ||
throw new Exception\InvalidArgumentException(sprintf( | ||
'$prefix argument to %s must be a string; received %s', | ||
__CLASS__, | ||
is_object($prefix) ? get_class($prefix) : gettype($prefix) | ||
)); | ||
} | ||
$this->prefix = $this->normalizePrefix($prefix); | ||
$this->middleware = $middleware; | ||
} | ||
|
||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) | ||
{ | ||
$path = $request->getUri()->getPath(); | ||
$path = $path ?: '/'; | ||
|
||
// Current path is shorter than decorator path | ||
if (strlen($path) < strlen($this->prefix)) { | ||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
|
||
// Current path does not match decorator path | ||
if (substr(strtolower($path), 0, strlen($this->prefix)) !== strtolower($this->prefix)) { | ||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
|
||
// Skip if match is not at a border ('/', '.', or end) | ||
$border = $this->getBorder($path); | ||
if ($border && '/' !== $border && '.' !== $border) { | ||
return $handler->{HANDLER_METHOD}($request); | ||
} | ||
|
||
// Trim off the part of the url that matches the prefix if it is non-empty | ||
$requestToProcess = (! empty($this->prefix) && $this->prefix !== '/') | ||
? $this->prepareRequestWithTruncatedPrefix($request) | ||
: $request; | ||
|
||
// Process our middleware. | ||
// If the middleware calls on the handler, the handler should be provided | ||
// the original request, as this indicates we've left the path-segregated | ||
// layer. | ||
return $this->middleware->process( | ||
$requestToProcess, | ||
new PathRequestHandlerDecorator($handler, $request) | ||
); | ||
} | ||
|
||
/** | ||
* @param string $path | ||
* @return string | ||
*/ | ||
private function getBorder($path) | ||
{ | ||
if ($this->prefix === '/') { | ||
return '/'; | ||
} | ||
|
||
$length = strlen($this->prefix); | ||
return strlen($path) > $length ? $path[$length] : ''; | ||
} | ||
|
||
/** | ||
* @return ServerRequestInterface | ||
*/ | ||
private function prepareRequestWithTruncatedPrefix(ServerRequestInterface $request) | ||
{ | ||
$uri = $request->getUri(); | ||
$path = $this->getTruncatedPath($this->prefix, $uri->getPath()); | ||
$new = $uri->withPath($path); | ||
return $request->withUri($new); | ||
} | ||
|
||
/** | ||
* @param string $segment | ||
* @param string $path | ||
* @return string | ||
*/ | ||
private function getTruncatedPath($segment, $path) | ||
{ | ||
if ($segment === $path) { | ||
// Decorated path and current path are the same; return empty string | ||
return ''; | ||
} | ||
|
||
$length = strlen($segment); | ||
if (strlen($path) > $length) { | ||
// Strip decorated path from start of current path | ||
return substr($path, $length); | ||
} | ||
|
||
if ('/' === substr($segment, -1)) { | ||
// Re-try by submitting with / stripped from end of segment | ||
return $this->getTruncatedPath(rtrim($segment, '/'), $path); | ||
} | ||
|
||
// Segment is longer than path; this is a problem. | ||
throw Exception\PathOutOfSyncException::forPath($this->prefix, $path); | ||
} | ||
|
||
/** | ||
* Ensures that the right-most slash is trimmed for prefixes of more than | ||
* one character, and that the prefix begins with a slash. | ||
* | ||
* @param string $prefix | ||
* @return string | ||
*/ | ||
private function normalizePrefix($prefix) | ||
{ | ||
$prefix = strlen($prefix) > 1 ? rtrim($prefix, '/') : $prefix; | ||
if ('/' !== substr($prefix, 0, 1)) { | ||
$prefix = '/' . $prefix; | ||
} | ||
return $prefix; | ||
} | ||
} |
Oops, something went wrong.