Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSR-15 Middleware Support #2555

Merged
merged 75 commits into from Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
da13a4e
add http-server-middleware dependency in composer.json
l0gicgate Dec 20, 2018
5f49eb5
add MiddlewareRunner, LegacyMiddlewareWrapper and supporting logic in…
l0gicgate Dec 20, 2018
d5ee2f2
convert existing middleware to PSR-15 synthax
l0gicgate Dec 20, 2018
8eeb1c6
add supporting test suite for all changes
l0gicgate Dec 20, 2018
6e14297
fix typo in README
l0gicgate Dec 20, 2018
b778f57
fix exceeded line lengths in multiple files
l0gicgate Dec 20, 2018
4948c6f
fix coding standard error in Route
l0gicgate Dec 20, 2018
9d831b4
add test coverage for App::setContainer() to ensure CallableResolver …
l0gicgate Dec 20, 2018
5164b7a
add test coverage for Route::add()
l0gicgate Dec 20, 2018
8b7b00b
fix typo in README
l0gicgate Dec 26, 2018
310f448
add ability to defer instantiation for PSR-15 middleware
l0gicgate Jan 17, 2019
53d081e
add supporting tests for deferred PSR-15 instantiation
l0gicgate Jan 17, 2019
f750764
fix RouteGroupInterface error
l0gicgate Jan 17, 2019
e7187a2
refactor middleware adding logic to detect type of middleware
l0gicgate Jan 23, 2019
b284fc4
add supporting tests
l0gicgate Jan 23, 2019
712e326
fix doc block in DeferredResolutionMiddlewareWrapper
l0gicgate Jan 23, 2019
95070ad
add setContainer method on CallableResolver interface and supporting …
l0gicgate Jan 23, 2019
a7881c0
refactor logic for App::setContainer() to update CallableResolver con…
l0gicgate Jan 23, 2019
0fb790f
rename DeferredResolutionMiddlewareWrapper and Psr7MiddlewareWrapper
l0gicgate Jan 24, 2019
8c38706
add doc blocs to middleware classes
l0gicgate Jan 24, 2019
9a95ba2
Separate routing detection from App into its own middleware
l0gicgate Feb 11, 2019
94949a1
add supporting test for RoutingDetectionMiddleware
l0gicgate Feb 11, 2019
dcdb6b8
replace string class names with string constants in AppTest
l0gicgate Feb 11, 2019
a2cf3fe
fix rebase errors
l0gicgate Feb 11, 2019
ca7497b
fix line length in AppTest
l0gicgate Feb 11, 2019
4fe2e5a
fix phpcs error
l0gicgate Feb 11, 2019
738388f
add deferred Router resolving for RoutingDetectionMiddleware
l0gicgate Feb 12, 2019
cac5e5c
make RouteGroup reference CallableResolver from Router instead of App
l0gicgate Feb 12, 2019
2460d1e
rename instances of LegacyMiddlewareWrapper to Psr7MiddlewareAdapter
l0gicgate Feb 12, 2019
5ef7c2c
fix synthax in README
l0gicgate Feb 12, 2019
a68ed58
add ClosureMiddleware and retrofit unit tests to use it
l0gicgate Feb 14, 2019
5bb76d6
refactor MiddlewareRunner to spawn independant runner for sub-request…
l0gicgate Feb 14, 2019
3e6c67a
remove callable support in add method to force MiddlewareInterface usage
l0gicgate Feb 14, 2019
bdcaad7
add deferred resolvers Container, CallableResolver and Router
l0gicgate Feb 14, 2019
98df6db
refactor tests to reflect deferred resolvers changes
l0gicgate Feb 14, 2019
1bf492e
remove files from #2380 patch skipped during rebase
l0gicgate Feb 14, 2019
0f692c9
reorganize getters and setters in App
l0gicgate Feb 14, 2019
3122b90
fix PHPCS errors
l0gicgate Feb 14, 2019
97af983
add test coverage for Router getDefaultInvocationStrategy and getCall…
l0gicgate Feb 15, 2019
d18bdac
add test for Routable DeferredMiddleware string resolution in add() m…
l0gicgate Feb 15, 2019
2f27250
add extra test coverage for OutputBufferingMiddleware
l0gicgate Feb 15, 2019
3922fe2
fix errors in README
l0gicgate Feb 17, 2019
47708f1
rename RoutingDetectionMiddleware to DispatchMiddleware
l0gicgate Feb 17, 2019
afaddbe
remove App setters and modify constructor, add Closure middleware sup…
l0gicgate Feb 18, 2019
7c6581c
make changes to CallableResolver interface
l0gicgate Feb 18, 2019
ba7b324
make changes to RouteGroupInterface, RouteInterface and RouterInterface
l0gicgate Feb 18, 2019
81c65a9
make changes to Routable, Route, RouteGroup and Router to support int…
l0gicgate Feb 18, 2019
a3ef232
change middleware ContainerInterface referencing
l0gicgate Feb 18, 2019
b9eae84
fix all supporting tests
l0gicgate Feb 18, 2019
819bb64
fix README docblocks
l0gicgate Feb 18, 2019
a18fb6d
add missing App tests
l0gicgate Feb 18, 2019
24bcd44
add missing Route tests
l0gicgate Feb 18, 2019
cd238dd
removing unused constants from RouterInterface
l0gicgate Feb 18, 2019
148f85d
add type hinting to App::getContainer()
l0gicgate Feb 18, 2019
392be8a
reorder and remove unused imports in multiple files
l0gicgate Feb 18, 2019
021518f
add return type to setters and add() method in MiddlewareRunner
l0gicgate Feb 18, 2019
6fe1b98
use bindTo() synthax instead of Closure::bind()
l0gicgate Feb 18, 2019
45adef9
Merge branch '4.x' of https://github.com/slimphp/Slim into Middleware
l0gicgate Feb 18, 2019
fac6cd6
remove unused statement in HttpUnauthorizedExceptionTest
l0gicgate Feb 18, 2019
e57573f
add closure support in DeferredResolutionMiddleware
l0gicgate Feb 18, 2019
e615860
refactor container declaration synthax order in AppTest
l0gicgate Feb 18, 2019
aa477c4
clean up App::group() and type hint Router::popGroup()
l0gicgate Feb 18, 2019
1737872
fix README typo
l0gicgate Feb 18, 2019
b349bb4
add missing type hints to Route methods and rename variable assignmen…
l0gicgate Feb 18, 2019
5dfaeb7
fix spelling mistake in doc block in RequestHandler invocation strategy
l0gicgate Feb 18, 2019
42c3d17
reorder methods in Routable
l0gicgate Feb 19, 2019
0fc5487
refactor multiple CallableResolverTest test cases
l0gicgate Feb 19, 2019
c452f0b
fix PHPCS error
l0gicgate Feb 19, 2019
ae18cf2
broaden callable support in DeferredResolutionMiddleware and add supp…
l0gicgate Feb 19, 2019
f5ed7b5
remove superfluous callable invocation in CallableResolverTest::testC…
l0gicgate Feb 22, 2019
6f7f77a
Reverse logic in ternary to remove negative test
akrabat Feb 22, 2019
357beb2
Remove unneeded temporary variable in DispatchMiddleware
akrabat Feb 22, 2019
5e8fc67
Always uses MiddlewareRunner::add() to add middleware
akrabat Feb 22, 2019
4bf9785
Change Route::__construct()'s $methods parameter to always be an array
akrabat Feb 22, 2019
9aa8c6b
Remove redundant conditional
akrabat Feb 22, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 22 additions & 10 deletions README.md
Expand Up @@ -46,8 +46,9 @@ use Slim\Psr7\Factory\StreamFactory;
$responseFactory = new DecoratedResponseFactory(new ResponseFactory(), new StreamFactory());
$app = new App($responseFactory);

// Add middlweare (LIFO stack)
$app->add(new ErrorMiddleware($app->getCallableResolver(), $responseFactory, true, true, true));
// Add middleware (LIFO stack)
$errorMiddleware = new ErrorMiddleware($app->getCallableResolver(), $responseFactory, true, true, true);
$app->add($errorMiddleware);

// Action
$app->get('/hello/{name}', function ($request, $response, $args) {
Expand Down Expand Up @@ -80,14 +81,17 @@ $serverRequestFactory = new ServerRequestCreator(
);

/**
* The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
* The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters
* @param ResponseFactoryInterface Any implementation of a ResponseFactory
* @param ContainerInterface|null Any implementation of a Container
* @param array Settings array
* @param CallableResolverInterface|null Any implementation of a CallableResolver
* @param RouterInterface|null Any implementation of a Router
*/
$app = new Slim\App($psr17Factory);
$app->get('/hello/{name}', function ($request, $response, $args) {
return $response->getBody()->write("Hello, " . $args['name']);
$response->getBody()->write("Hello, " . $args['name']);
return $response;
});

/**
Expand All @@ -111,14 +115,17 @@ $responseFactory = new ResponseFactory();
$serverRequestFactory = new ServerRequestFactory();

/**
* The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
* The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters
* @param ResponseFactoryInterface Any implementation of a ResponseFactory
* @param ContainerInterface|null Any implementation of a Container
* @param array Settings array
* @param CallableResolverInterface|null Any implementation of a CallableResolver
* @param RouterInterface|null Any implementation of a Router
*/
$app = new Slim\App($responseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
return $response->getBody()->write("Hello, " . $args['name']);
$response->getBody()->write("Hello, " . $args['name']);
return $response;
});

/**
Expand Down Expand Up @@ -155,12 +162,14 @@ $decoratedResponseFactory = new DecoratedResponseFactory($responseFactory, $stre
$serverRequestFactory = new ServerRequestFactory();

/**
* The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
* The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters
* Note that we pass in the decorated response factory which will give us access to the Slim\Http
* decorated Response methods like withJson()
* @param ResponseFactoryInterface Any implementation of a ResponseFactory
* @param ContainerInterface|null Any implementation of a Container
* @param array Settings array
* @param CallableResolverInterface|null Any implementation of a CallableResolver
* @param RouterInterface|null Any implementation of a Router
*/
$app = new Slim\App($decoratedResponseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
Expand Down Expand Up @@ -189,14 +198,17 @@ use Http\Factory\Guzzle\ResponseFactory;
$responseFactory = new ResponseFactory();

/**
* The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
* The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters
* @param ResponseFactoryInterface Any implementation of a ResponseFactory
* @param ContainerInterface|null Any implementation of a Container
* @param array Settings array
* @param CallableResolverInterface|null Any implementation of a CallableResolver
* @param RouterInterface|null Any implementation of a Router
*/
$app = new Slim\App($responseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
return $response->getBody()->write("Hello, " . $args['name']);
$response->getBody()->write("Hello, " . $args['name']);
return $response;
});

/**
Expand All @@ -220,7 +232,7 @@ For more information on how to configure your web server, see the [Documentation
To execute the test suite, you'll need to install all development dependencies.

```bash
$ git clone https://github.com/slimphp/Slim-Http
$ git clone https://github.com/slimphp/Slim
$ composer install
$ composer test
```
Expand Down
172 changes: 56 additions & 116 deletions Slim/App.php
Expand Up @@ -11,19 +11,22 @@

namespace Slim;

use Closure;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use RuntimeException;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\RouteGroupInterface;
use Slim\Interfaces\RouteInterface;
use Slim\Interfaces\RouterInterface;
use Slim\Middleware\RoutingMiddleware;
use Slim\Middleware\ClosureMiddleware;
use Slim\Middleware\DeferredResolutionMiddleware;
use Slim\Middleware\DispatchMiddleware;

/**
* App
Expand All @@ -34,8 +37,6 @@
*/
class App implements RequestHandlerInterface
{
use MiddlewareAwareTrait;

/**
* Current version
*
Expand All @@ -55,6 +56,11 @@ class App implements RequestHandlerInterface
*/
protected $callableResolver;

/**
* @var MiddlewareRunner
*/
protected $middlewareRunner;

/**
* @var RouterInterface
*/
Expand All @@ -70,7 +76,7 @@ class App implements RequestHandlerInterface
*/
protected $settings = [
'httpVersion' => '1.1',
'routerCacheFile' => false,
'routerCacheFile' => null,
];

/********************************************************************************
Expand All @@ -80,54 +86,60 @@ class App implements RequestHandlerInterface
/**
* Create new application
*
* @param ResponseFactoryInterface $responseFactory
* @param ContainerInterface|null $container
* @param array $settings
* @param ResponseFactoryInterface $responseFactory
* @param ContainerInterface|null $container
* @param array $settings
* @param CallableResolverInterface $callableResolver
* @param RouterInterface $router
*/
public function __construct(
ResponseFactoryInterface $responseFactory,
ContainerInterface $container = null,
array $settings = []
array $settings = [],
CallableResolverInterface $callableResolver = null,
RouterInterface $router = null
) {
$this->responseFactory = $responseFactory;
$this->container = $container;
$this->callableResolver = $callableResolver ?? new CallableResolver($container);
$this->addSettings($settings);
}

/**
* Get container
*
* @return ContainerInterface|null
*/
public function getContainer()
{
return $this->container;
$this->router = $router ?? new Router($responseFactory, $this->callableResolver);
$routerCacheFile = $this->getSetting('routerCacheFile', null);
$this->router->setCacheFile($routerCacheFile);

$this->middlewareRunner = new MiddlewareRunner();
$this->addMiddleware(new DispatchMiddleware($this->router));
}

/**
* Set container
*
* @param ContainerInterface $container
* @param MiddlewareInterface|string|callable $middleware
* @return self
*/
public function setContainer(ContainerInterface $container)
public function add($middleware): self
{
$this->container = $container;
if (is_string($middleware)) {
$middleware = new DeferredResolutionMiddleware($middleware, $this->container);
} elseif ($middleware instanceof Closure) {
$middleware = new ClosureMiddleware($middleware);
} elseif (!($middleware instanceof MiddlewareInterface)) {
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
throw new RuntimeException(
'Parameter 1 of `Slim\App::add()` must be a closure or an object/class name '.
'referencing an implementation of MiddlewareInterface.'
);
}

return $this->addMiddleware($middleware);
}

/**
* Add middleware
*
* This method prepends new middleware to the app's middleware stack.
*
* @param callable|string $callable The callback routine
*
* @return static
* @param MiddlewareInterface $middleware
* @return self
*/
public function add($callable)
public function addMiddleware(MiddlewareInterface $middleware): self
{
return $this->addMiddleware(
new DeferredCallable($callable, $this->getCallableResolver())
);
$this->middlewareRunner->add($middleware);
return $this;
}

/********************************************************************************
Expand Down Expand Up @@ -189,17 +201,17 @@ public function addSetting(string $key, $value)
}

/********************************************************************************
* Setter and getter methods
* Getter methods
*******************************************************************************/

/**
* Set callable resolver
* Get container
*
* @param CallableResolverInterface $resolver
* @return ContainerInterface|null
*/
public function setCallableResolver(CallableResolverInterface $resolver)
public function getContainer(): ?ContainerInterface
{
$this->callableResolver = $resolver;
return $this->container;
}

/**
Expand All @@ -209,42 +221,16 @@ public function setCallableResolver(CallableResolverInterface $resolver)
*/
public function getCallableResolver(): CallableResolverInterface
{
if (! $this->callableResolver instanceof CallableResolverInterface) {
$this->callableResolver = new CallableResolver($this->container);
}

return $this->callableResolver;
}

/**
* Set router
*
* @param RouterInterface $router
*/
public function setRouter(RouterInterface $router)
{
$this->router = $router;
}

/**
* Get router
*
* @return RouterInterface
*/
public function getRouter(): RouterInterface
{
if (! $this->router instanceof RouterInterface) {
$router = new Router();
$resolver = $this->getCallableResolver();
if ($resolver instanceof CallableResolverInterface) {
$router->setCallableResolver($resolver);
}
$routerCacheFile = $this->getSetting('routerCacheFile', false);
$router->setCacheFile($routerCacheFile);

$this->router = $router;
}

return $this->router;
}

Expand Down Expand Up @@ -377,7 +363,7 @@ public function map(array $methods, string $pattern, $callable): RouteInterface
*/
public function redirect(string $from, $to, int $status = 302): RouteInterface
{
$handler = function ($request, ResponseInterface $response) use ($to, $status) {
$handler = function (ServerRequestInterface $request, ResponseInterface $response) use ($to, $status) {
return $response->withHeader('Location', (string)$to)->withStatus($status);
};

Expand All @@ -398,17 +384,9 @@ public function redirect(string $from, $to, int $status = 302): RouteInterface
*/
public function group(string $pattern, $callable): RouteGroupInterface
{
$router = $this->getRouter();

/** @var RouteGroup $group */
$group = $router->pushGroup($pattern, $callable);
if ($this->callableResolver instanceof CallableResolverInterface) {
$group->setCallableResolver($this->callableResolver);
}

$group = $this->router->pushGroup($pattern, $callable);
$group($this);
$router->popGroup();

$this->router->popGroup();
return $group;
}

Expand Down Expand Up @@ -442,13 +420,7 @@ public function run(ServerRequestInterface $request): void
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$httpVersion = $this->getSetting('httpVersion');
$response = $this->responseFactory
->createResponse(200, '')
->withProtocolVersion($httpVersion)
->withHeader('Content-Type', 'text/html; charset=UTF-8');

$response = $this->callMiddlewareStack($request, $response);
$response = $this->middlewareRunner->run($request);

/**
* This is to be in compliance with RFC 2616, Section 9.
Expand All @@ -465,36 +437,4 @@ public function handle(ServerRequestInterface $request): ResponseInterface

return $response;
}

/**
* Invoke application
*
* This method implements the middleware interface. It receives
* Request and Response objects, and it returns a Response object
* after compiling the routes registered in the Router and dispatching
* the Request object to the appropriate Route callback routine.
*
* @param ServerRequestInterface $request The most recent Request object
* @param ResponseInterface $response The most recent Response object
*
* @return ResponseInterface
*
* @throws HttpNotFoundException
* @throws HttpMethodNotAllowedException
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
/** @var RoutingResults|null $routingResults */
$routingResults = $request->getAttribute('routingResults');

// If routing hasn't been done, then do it now so we can dispatch
if ($routingResults === null) {
$router = $this->getRouter();
$routingMiddleware = new RoutingMiddleware($router);
$request = $routingMiddleware->performRouting($request);
}

$route = $request->getAttribute('route');
return $route->run($request, $response);
}
}