From 3f9b6f507acb8f7ebcf342bb9d37843ba8fa8219 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 13 Jan 2016 16:34:13 -0600 Subject: [PATCH 01/18] Created tests for new pipeline functionality Tests the new behavior: ```php 'middleware_pipeline' => [ [ 'middleware' => /* ... */ ], [ 'middleware' => /* ... */ ], ], ``` And also provides tests verifying that the old methodology of having `pre_` and `post_routing` keys works, but raises deprecation notices. --- test/Container/ApplicationFactoryTest.php | 250 +++++++++++++++++++++- 1 file changed, 249 insertions(+), 1 deletion(-) diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php index 194c970a..b6801c76 100644 --- a/test/Container/ApplicationFactoryTest.php +++ b/test/Container/ApplicationFactoryTest.php @@ -25,6 +25,7 @@ use Zend\Expressive\Router\FastRouteRouter; use Zend\Expressive\Router\Route; use Zend\Expressive\Router\RouterInterface; +use Zend\Stratigility\MiddlewarePipe; use Zend\Stratigility\Route as StratigilityRoute; use ZendTest\Expressive\ContainerTrait; use ZendTest\Expressive\TestAsset\InvokableMiddleware; @@ -223,7 +224,14 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo $this->injectServiceInContainer($this->container, 'config', $config); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $app = $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); $r = new ReflectionProperty($app, 'pipeline'); $r->setAccessible(true); @@ -275,7 +283,14 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationAfterSettingRout $this->injectServiceInContainer($this->container, 'config', $config); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $app = $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); $r = new ReflectionProperty($app, 'pipeline'); $r->setAccessible(true); @@ -394,7 +409,6 @@ public function uncallableMiddleware() } /** - * @group fail * @group piping * @dataProvider uncallableMiddleware */ @@ -438,7 +452,14 @@ function () { $this->injectServiceInContainer($this->container, 'Hello', function () { }); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); } /** @@ -627,7 +648,14 @@ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() $this->injectServiceInContainer($this->container, 'config', $config); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $app = $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); $r = new ReflectionProperty($app, 'pipeline'); $r->setAccessible(true); @@ -767,6 +795,12 @@ public function testExceptionIsRaisedInCaseOfInvalidPreRoutingMiddlewarePipeline ContainerException\InvalidArgumentException::class, 'Pre-routing middleware collection must be an array; received "string"' ); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $this->factory->__invoke($this->container->reveal()); } @@ -786,4 +820,218 @@ public function testExceptionIsRaisedInCaseOfInvalidPostRoutingMiddlewarePipelin ); $this->factory->__invoke($this->container->reveal()); } + + public function middlewarePipelines() + { + // @codingStandardsIgnoreStart + return [ + + ]; + // @codingStandardsIgnoreEnd + } + + public function testWillCreatePipelineBasedOnMiddlewareConfiguration() + { + // @codingStandardsIgnoreStart + $api = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $dynamicPath = clone $api; + $noPath = clone $api; + $goodbye = clone $api; + $pipelineFirst = clone $api; + $hello = clone $api; + $pipelineLast = clone $api; + + $this->injectServiceInContainer($this->container, 'DynamicPath', $dynamicPath); + $this->injectServiceInContainer($this->container, 'Goodbye', $goodbye); + $this->injectServiceInContainer($this->container, 'Hello', $hello); + + $pipeline = [ + [ 'path' => '/api', 'middleware' => $api ], + [ 'path' => '/dynamic-path', 'middleware' => 'DynamicPath' ], + ['middleware' => $noPath], + ['middleware' => 'Goodbye'], + ['middleware' => [ + $pipelineFirst, + 'Hello', + $pipelineLast, + ]], + ]; + + $config = ['middleware_pipeline' => $pipeline]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + + $this->assertAttributeSame( + false, + 'routeMiddlewareIsRegistered', + $app, + 'Route middleware was registered when it should not have been' + ); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertCount(5, $pipeline, 'Did not get expected pipeline count!'); + + $test = $pipeline->dequeue(); + $this->assertEquals('/api', $test->path); + $this->assertSame($api, $test->handler); + + // Lazy middleware is not marshaled until invocation + $test = $pipeline->dequeue(); + $this->assertEquals('/dynamic-path', $test->path); + $this->assertNotSame($dynamicPath, $test->handler); + $this->assertInstanceOf(Closure::class, $test->handler); + + $test = $pipeline->dequeue(); + $this->assertEquals('/', $test->path); + $this->assertSame($noPath, $test->handler); + + // Lazy middleware is not marshaled until invocation + $test = $pipeline->dequeue(); + $this->assertEquals('/', $test->path); + $this->assertNotSame($goodbye, $test->handler); + $this->assertInstanceOf(Closure::class, $test->handler); + + $test = $pipeline->dequeue(); + $nestedPipeline = $test->handler; + $this->assertInstanceOf(MiddlewarePipe::class, $nestedPipeline); + + $test = $nestedPipeline->dequeue(); + $this->assertSame($pipelineFirst, $test->handler); + + // Lazy middleware is not marshaled until invocation + $test = $nestedPipeline->dequeue(); + $this->assertNotSame($hello, $test->handler); + $this->assertInstanceOf(Closure::class, $test->handler); + + $test = $nestedPipeline->dequeue(); + $this->assertSame($pipelineLast, $test->handler); + } + + public function mixedMiddlewarePipelines() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + $pre = ['middleware' => clone $middleware]; + $post = ['middleware' => clone $middleware]; + $pipelined = ['middleware' => clone $middleware]; + return [ + 'pre_routing' => [['middleware_pipeline' => ['pre_routing' => [$pre], $pipelined]]], + 'post_routing' => [['middleware_pipeline' => ['post_routing' => [$post], $pipelined]]], + 'pre_and_post' => [['middleware_pipeline' => ['pre_routing' => [$pre], 'post_routing' => [$post], $pipelined]]], + ]; + // @codingStandardsIgnoreEnd + } + + /** + * @dataProvider mixedMiddlewarePipelines + */ + public function testRaisesExceptionIfMiddlewarePipelineConfigurationMixesMiddlewareAndPreOrPostRouting($config) + { + $this->injectServiceInContainer($this->container, 'config', $config); + + $this->setExpectedException(InvalidArgumentException::class, 'mix of middleware'); + $this->factory->__invoke($this->container->reveal()); + } + + public function middlewarePipelinesWithPreOrPostRouting() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + $config = ['middleware' => clone $middleware]; + return [ + 'pre_routing' => [['pre_routing' => [$config]]], + 'post_routing' => [['post_routing' => [$config]]], + ]; + // @codingStandardsIgnoreEnd + } + + /** + * @dataProvider middlewarePipelinesWithPreOrPostRouting + */ + public function testRaisesDeprecationNoticeForUsageOfPreOrPostRoutingPipelineConfiguration($config) + { + $config = ['middleware_pipeline' => $config]; + $this->injectServiceInContainer($this->container, 'config', $config); + + // @codingStandardsIgnoreStart + $triggered = false; + set_error_handler(function ($errno, $errmsg) use (&$triggered) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + + $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); + $this->assertTrue($triggered, 'Deprecation notice was not triggered!'); + } + + public function testRaisesExceptionIfRoutesAreDefinedPipelineIsPopulatedAndPipelineDoesNotProvideRoutingMiddleware() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $config = [ + 'middleware_pipeline' => [ + clone $middleware, + ], + 'routes' => [ + [ + 'path' => '/', + 'middleware' => clone $middleware, + 'allowed_methods' => [ 'GET' ], + ], + ], + ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $this->setExpectedException(InvalidArgumentException::class, 'routing middleware'); + $this->factory->__invoke($this->container->reveal()); + } + + public function configWithRoutesButNoPipeline() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $routes = [ + [ + 'path' => '/', + 'middleware' => clone $middleware, + 'allowed_methods' => [ 'GET' ], + ], + ]; + + return [ + 'no-pipeline-defined' => [['routes' => $routes]], + 'empty-pipeline' => [['middleware_pipeline' => [], 'routes' => $routes]], + 'null-pipeline' => [['middleware_pipeline' => null, 'routes' => $routes]], + ]; + } + + /** + * @dataProvider configWithRoutesButNoPipeline + */ + public function testProvidingRoutesAndNoPipelineImplicitlyRegistersRoutingMiddleware($config) + { + $this->injectServiceInContainer($this->container, 'config', $config); + $app = $this->factory->__invoke($this->container->reveal()); + $this->assertAttributeSame(true, 'routeMiddlewareIsRegistered', $app); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertCount(1, $pipeline, 'Did not get expected pipeline count!'); + $test = $pipeline->dequeue(); + $this->assertEquals('/', $test->path); + $this->assertSame([$app, 'routeMiddleware'], $test->handler); + } } From 748673bf4ff8882786b72f090a0c1de0f818b4b9 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 09:53:17 -0600 Subject: [PATCH 02/18] Simplify the middleware pipeline configuration This patch simplifies the middleware pipeline configuration, by removing the `pre_routing` and `post_routing` keys; this also allows developers to replace the routing middleware if desired by omitting the default from the pipeline configuration. All items in the pipeline follow the same structure that was required of the `pre_` and `post_routing` keys, with one exception: the routing middleware is specified via a constant value. --- doc/book/container/factories.md | 15 +- doc/book/cookbook/custom-404-page-handling.md | 19 +- doc/book/cookbook/debug-toolbars.md | 8 +- doc/book/cookbook/route-specific-pipeline.md | 22 +- ...ting-locale-depending-routing-parameter.md | 14 +- ...etting-locale-without-routing-parameter.md | 15 +- doc/book/cookbook/using-a-base-path.md | 11 +- .../cookbook/using-zend-form-view-helpers.md | 17 +- doc/book/helpers/body-parse.md | 13 +- doc/book/helpers/server-url-helper.md | 19 +- doc/book/helpers/url-helper.md | 17 +- doc/book/migration/bookdown.json | 7 + doc/book/migration/rc-to-v1.md | 121 +++++++++ doc/book/usage-examples.md | 56 +++-- doc/bookdown.json | 3 +- mkdocs.yml | 1 + src/Application.php | 145 +++++------ src/Container/ApplicationFactory.php | 236 ++++++++++++------ test/ApplicationTest.php | 44 ---- test/Container/ApplicationFactoryTest.php | 126 +++++----- test/RouteMiddlewareTest.php | 26 -- 21 files changed, 524 insertions(+), 411 deletions(-) create mode 100644 doc/book/migration/bookdown.json create mode 100644 doc/book/migration/rc-to-v1.md diff --git a/doc/book/container/factories.md b/doc/book/container/factories.md index e2f28e82..a4acf6dc 100644 --- a/doc/book/container/factories.md +++ b/doc/book/container/factories.md @@ -39,18 +39,15 @@ order to seed the `Application` instance: ```php 'middleware_pipeline' => [ - // An array of middleware to register prior to registration of the - // routing middleware: - 'pre_routing' => [ - ], - // An array of middleware to register after registration of the - // routing middleware: - 'post_routing' => [ - ], + // An array of middleware to register. + [ /* ... */ ], + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + [ /* ... */ ], ], ``` - Each item of each array must be an array itself, with the following structure: + Each item of the array, other than the entry for routing middleware, must be + an array itself, with the following structure: ```php [ diff --git a/doc/book/cookbook/custom-404-page-handling.md b/doc/book/cookbook/custom-404-page-handling.md index 701a838c..7b56b090 100644 --- a/doc/book/cookbook/custom-404-page-handling.md +++ b/doc/book/cookbook/custom-404-page-handling.md @@ -86,22 +86,17 @@ From there, you still need to register the middleware. This middleware is not routed, and thus needs to be piped to the application instance. You can do this via either configuration, or manually. -To do this via configuration, add an entry under the `post_routing` key of the -`middleware_pipeline` configuration: +To do this via configuration, add an entry under the `middleware_pipeline` +configuration, after the routing middleware: ```php 'middleware_pipeline' => [ - 'pre_routing' => [ - [ - //... - ], - - ], - 'post_routing' => [ - [ - 'middleware' => 'Application\NotFound', - ], + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + [ + 'middleware' => 'Application\NotFound', ], + /* ... */ ], ``` diff --git a/doc/book/cookbook/debug-toolbars.md b/doc/book/cookbook/debug-toolbars.md index becc080a..52e03191 100644 --- a/doc/book/cookbook/debug-toolbars.md +++ b/doc/book/cookbook/debug-toolbars.md @@ -74,11 +74,9 @@ return [ ], ], 'middleware_pipeline' => [ - 'pre_routing' => [ - [ - 'middleware' => [ - PhpDebugBarMiddleware::class, - ], + [ + 'middleware' => [ + PhpDebugBarMiddleware::class, ], ], ], diff --git a/doc/book/cookbook/route-specific-pipeline.md b/doc/book/cookbook/route-specific-pipeline.md index 739f1519..01e1e9b3 100644 --- a/doc/book/cookbook/route-specific-pipeline.md +++ b/doc/book/cookbook/route-specific-pipeline.md @@ -160,23 +160,21 @@ When either of these approaches are used, the individual middleware listed This approach is essentially equivalent to creating a factory that returns a middleware pipeline. -## What about pre/post pipeline middleware? +## What about pipeline middleware configuration? -What if you want to do this with pre- or post-pipeline middleware? The answer is -that the syntax is exactly the same! +What if you want to do this with your pipeline middleware configuration? The +answer is that the syntax is exactly the same! ```php return [ 'middleware_pipeline' => [ - 'pre_routing' => [ - [ - 'path' => '/api', - 'middleware' => [ - 'AuthenticationMiddleware', - 'AuthorizationMiddleware', - 'BodyParsingMiddleware', - 'ValidationMiddleware', - ], + [ + 'path' => '/api', + 'middleware' => [ + 'AuthenticationMiddleware', + 'AuthorizationMiddleware', + 'BodyParsingMiddleware', + 'ValidationMiddleware', ], ], ], diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index c1f0ba33..6aaffe0e 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -287,7 +287,8 @@ portable way of doing delegator factories. ### Use middleware -Alternately, use `pre_routing` middleware to accomplish the task; the middleware +Alternately, use the middleware pipeline to accomplish the task. Register the +middleware early in the pipeline (before the routing middleware); the middleware will get both the observer and application as dependencies, and simply register the observer with the application: @@ -328,13 +329,10 @@ return [ /* ... */ ], 'middleware_pipeline' => [ - 'pre_routing' => [ - [ 'middleware' => LocalizationObserverMiddleware::class ], - /* ... */ - ], - 'post_routing' => [ - /* ... */ - ], + [ 'middleware' => LocalizationObserverMiddleware::class ], + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + /* ... */ ], ]; ``` diff --git a/doc/book/cookbook/setting-locale-without-routing-parameter.md b/doc/book/cookbook/setting-locale-without-routing-parameter.md index 28b49ce3..269ecd3d 100644 --- a/doc/book/cookbook/setting-locale-without-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-without-routing-parameter.md @@ -102,19 +102,16 @@ return [ ] 'middleware_pipeline' => [ - 'pre_routing' => [ - [ - 'middleware' => [ - Application\I18n\SetLanguageMiddleware::class, - /* ... */ - ], + [ + 'middleware' => [ + Application\I18n\SetLanguageMiddleware::class, /* ... */ ], ], - 'post_routing' => [ - /* ... */ - ], + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + + /* ... */ ], ]; ``` diff --git a/doc/book/cookbook/using-a-base-path.md b/doc/book/cookbook/using-a-base-path.md index 890ca7c5..246f1d7a 100644 --- a/doc/book/cookbook/using-a-base-path.md +++ b/doc/book/cookbook/using-a-base-path.md @@ -37,8 +37,8 @@ RewriteRule (.*) ./public/$1 The above step ensures that clients can hit the website. Now we need to ensure that the application can route to middleware! -To do this, we will add pre_routing pipeline middleware to intercept the -request, and rewrite the URL accordingly. +To do this, we will add pipeline middleware to intercept the request, and +rewrite the URL accordingly. At the time of writing, we have two suggestions: @@ -106,10 +106,9 @@ return [ /* ... */ ], 'middleware_pipeline' => [ - 'pre_routing' => [ - [ 'middleware' => [ Blast\BaseUrl\BaseUrlMiddleware::class ] ], - /* ... */ - ], + [ 'middleware' => [ Blast\BaseUrl\BaseUrlMiddleware::class ] ], + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, /* ... */ ], ]; diff --git a/doc/book/cookbook/using-zend-form-view-helpers.md b/doc/book/cookbook/using-zend-form-view-helpers.md index aaf3bd71..e0d45c81 100644 --- a/doc/book/cookbook/using-zend-form-view-helpers.md +++ b/doc/book/cookbook/using-zend-form-view-helpers.md @@ -20,8 +20,8 @@ You have three options: - Replace the `HelperPluginManager` factory with your own; or - Add a delegator factory to or extend the `HelperPluginManager` service to inject the additional helper configuration; or -- Add pre_routing pipeline middleware that composes the `HelperPluginManager` - and configures it. +- Add pipeline middleware that composes the `HelperPluginManager` and configures + it. ## Replacing the HelperPluginManager factory @@ -161,7 +161,7 @@ $container[HelperPluginManager::class] = $container->extend( ## Pipeline middleware -Another option is to use pre_routing pipeline middleware. This approach will +Another option is to use pipeline middleware. This approach will require that the middleware execute on every request, which introduces (very slight) performance overhead. However, it's a portable method that works regardless of the container implementation you choose. @@ -224,13 +224,10 @@ return [ /* ... */ ], 'middleware_pipeline' => [ - 'pre_routing' => [ - ['middleware' => Your\Application\FormHelpersMiddleware::class], - /* ... */ - ], - 'post_routing' => [ - /* ... */ - ], + ['middleware' => Your\Application\FormHelpersMiddleware::class], + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + /* ... */ ], ]; ``` diff --git a/doc/book/helpers/body-parse.md b/doc/book/helpers/body-parse.md index 85c81ec6..82cdfaf2 100644 --- a/doc/book/helpers/body-parse.md +++ b/doc/book/helpers/body-parse.md @@ -26,7 +26,7 @@ $app->pipe(BodyParamsMiddleware::class); $app->run(); ``` -or as `pre_routing` pipeline middleware: +or as pipeline middleware: ```php // config/autoload/middleware-pipeline.global.php @@ -43,13 +43,10 @@ return [ ], ], 'middleware_pipeline' => [ - 'pre_routing' => [ - [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class ], - /* ... */ - ], - 'post_routing' => [ - /* ... */ - ], + [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class ], + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + /* ... */ ], ]; ``` diff --git a/doc/book/helpers/server-url-helper.md b/doc/book/helpers/server-url-helper.md index ed03a466..53a2f9ce 100644 --- a/doc/book/helpers/server-url-helper.md +++ b/doc/book/helpers/server-url-helper.md @@ -47,7 +47,8 @@ As such, you will need to: - Register the `ServerUrlHelper` as a service in your container. - Register the `ServerUrlMiddleware` as a service in your container. -- Register the `ServerUrlMiddleware` as pre_routing pipeline middleware. +- Register the `ServerUrlMiddleware` as pipeline middleware, early in the + pipeline. The following examples demonstrate registering the services. @@ -89,9 +90,10 @@ $app->pipe(ServerUrlMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ -// 'pre_routing' => [ -// ['middleware' => ServerUrlMiddleware::class], -// ], +// ['middleware' => ServerUrlMiddleware::class], +// /* ... */ +// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// /* ... */ // ], // ] ``` @@ -110,18 +112,17 @@ return [ ], ], 'middleware_pipeline' => [ - 'pre_routing' => [ - ['middleware' => ServerUrlMiddleware::class], - ], + ['middleware' => ServerUrlMiddleware::class], + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, ], -] +]; ``` > ### Skeleton configures helpers > > If you started your project using the Expressive skeleton package, the > `ServerUrlHelper` and `ServerUrlMiddleware` factories are already registered -> for you, as is the `ServerUrlMiddleware` pre_routing pipeline middleware. +> for you, as is the `ServerUrlMiddleware` pipeline middleware. ## Using the helper in middleware diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 89cf998a..6159077f 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -66,7 +66,8 @@ following steps should be followed to register and configure the helper: factory. - Register the `UrlHelperMiddleware` as a service in your container, using the provided factory. -- Register the `UrlHelperMiddleware` as pre_routing pipeline middleware. +- Register the `UrlHelperMiddleware` as pipeline middleware, early in the + pipeline. ### Registering the helper service @@ -123,9 +124,10 @@ $app->pipe(UrlHelperMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ -// 'pre_routing' => [ -// ['middleware' => UrlHelperMiddleware::class], -// ], +// ['middleware' => UrlHelperMiddleware::class], +// /* ... */ +// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// /* ... */ // ], // ] ``` @@ -142,9 +144,8 @@ return [ ], ], 'middleware_pipeline' => [ - 'pre_routing' => [ - ['middleware' => UrlHelperMiddleware::class], - ], + ['middleware' => UrlHelperMiddleware::class], + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, ], ] ``` @@ -153,7 +154,7 @@ return [ > > If you started your project using the Expressive skeleton package, the > `UrlHelper` and `UrlHelperMiddleware` factories are already registered for -> you, as is the `UrlHelperMiddleware` pre_routing pipeline middleware. +> you, as is the `UrlHelperMiddleware` pipeline middleware. ## Using the helper in middleware diff --git a/doc/book/migration/bookdown.json b/doc/book/migration/bookdown.json new file mode 100644 index 00000000..3dc87476 --- /dev/null +++ b/doc/book/migration/bookdown.json @@ -0,0 +1,7 @@ +{ + "title": "Migration", + "content": [ + {"From RC5 and Earlier": "rc-to-v1.md"} + ] +} + diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md new file mode 100644 index 00000000..ac394fc9 --- /dev/null +++ b/doc/book/migration/rc-to-v1.md @@ -0,0 +1,121 @@ +# Migration from RC5 or earlier + +RC6 provides a breaking change intended to simplify the creation of the +middleware pipeline and to allow replacing the routing middleware. Doing so, +however, required a change to the `middleware_pipeline` configuration. + +The changes are done in such a way as to honor configuration from RC5 and +earlier, but using such configuration will now prompt you to update your +application. This document describes how to do so. + +## Configuration for RC5 and earlier + +RC5 and earlier defined the default `middleware_pipeline` configuration as follows: + +```php +return [ + 'middleware_pipeline' => [ + // An array of middleware to register prior to registration of the + // routing middleware + 'pre_routing' => [ + //[ + // Required: + // 'middleware' => 'Name or array of names of middleware services and/or callables', + // Optional: + // 'path' => '/path/to/match', + // 'error' => true, + //], + [ + 'middleware' => [ + Helper\ServerUrlMiddleware::class, + Helper\UrlHelperMiddleware::class, + ], + ], + ], + + // An array of middleware to register after registration of the + // routing middleware + 'post_routing' => [ + //[ + // Required: + // 'middleware' => 'Name of middleware service, or a callable', + // Optional: + // 'path' => '/path/to/match', + // 'error' => true, + //], + ], + ], +] +``` + +## Configuration for RC6 and above + +RC6 and later change the configuration to remove the `pre_routing` and +`post_routing` keys. However, individual items within the array retain the same +format as middleware inside those keys, namely: + +```php +[ + // Required: + 'middleware' => 'Name of middleware service, or a callable', + // Optional: + // 'path' => '/path/to/match', + // 'error' => true, +] +``` + +Additionally, the routing middleware itself now becomes one item in the array, +and is **required** if you have defined any routes. This change gives you full +control over the flow of the pipeline. + +To specify the routing middleware, use the constant +`Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` in place of +a middleware array; this has the value `EXPRESSIVE_ROUTING_MIDDLEWARE`, if you +do not want to import the class. + +As such, the default configuration now becomes: + +```php +return [ + 'middleware_pipeline' => [ + // An array of middleware to pipe to the application. + // Each item is of the following structure: + // [ + // // Required: + // 'middleware' => 'Name or array of names of middleware services and/or callables', + // // Optional: + // 'path' => '/path/to/match', + // 'error' => true, + // ], + [ + 'middleware' => [ + Helper\ServerUrlMiddleware::class, + Helper\UrlHelperMiddleware::class, + ], + ], + + // The following is an entry for the routing middleware: + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + + // Place error handling middleware after the routing middleware. + ], +] +``` + +To update an existing application: + +- Promote all `pre_routing` middleware up a level, and remove the `pre_routing` + key. +- Add the entry for `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` + immediately following any `pre_routing` middleware, and before any + `post_routing` middleware. +- Promote all `post_routing` middleware up a level, and remove the + `post_routing` key. + +Once you have made the above changes, you should no longer receive deprecation +notices when running your application. + +## Timeline + +Support for the `pre_routing` and `post_routing` configuration will be removed +with the 1.1.0 release. diff --git a/doc/book/usage-examples.md b/doc/book/usage-examples.md index c9fdb9b3..a643538b 100644 --- a/doc/book/usage-examples.md +++ b/doc/book/usage-examples.md @@ -451,26 +451,16 @@ return [ // etc. ], 'middleware_pipeline' => [ - 'pre_routing' => [ - // See specification below - ], - 'post_routing' => [ - // See specification below - ], + // See specification below ], ]; ``` -The key to note is `middleware_pipeline`, which can have two subkeys, -`pre_routing` and `post_routing`. Each accepts an array of middlewares to -register in the pipeline; they will each be `pipe()`'d to the Application in the -order specified. Those specified `pre_routing` will be registered before any -routes, and thus before the routing middleware, while those specified -`post_routing` will be `pipe()`'d afterwards (again, also in the order -specified). +The key to note is `middleware_pipeline`, which is an array of middlewares to +register in the pipeline; each will each be `pipe()`'d to the Application in the +order specified. -Each middleware specified in either `pre_routing` or `post_routing` must be in -the following form: +Each middleware specified must be in the following form: ```php [ @@ -482,8 +472,40 @@ the following form: ] ``` -Middleware may be any callable, `Zend\Stratigility\MiddlewareInterface` -implementation, or a service name that resolves to one of the two. +There is one exception to the above rule: to specify the *routing* middleware +that Expressive provides, use the value +`Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE`; this will be +caught by the factory and expanded: + +```php +return [ + 'middleware_pipeline' => [ + [ /* ... */ ], + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + [ /* ... */ ], + ], +]; +``` + +> #### Place routing middleware correctly +> +> If you are defining routes *and* defining other middleware for the pipeline, +> you **must** add the routing middleware. When you do so, make sure you put +> it at the appropriate location in the pipeline. +> +> Typically, you will place any middleware you want to execute on all requests +> prior to the routing middleware. This includes utilities for bootstrapping +> the application (such as injection of the `UrlHelper` or `ServerUrlHelper`), +> utilities for injecting common response headers (such as CORS support), etc. +> +> Place *error* middleware *after* the routing middleware. This is middleware +> that should only execute if routing fails or routed middleware cannot complete +> the response. + +Middleware items may be any callable, `Zend\Stratigility\MiddlewareInterface` +implementation, or a service name that resolves to one of the two. Additionally, +you can specify an array of such values; these will be composed in a single +`Zend\Stratigility\MiddlewarePipe` instance, allowing layering of middleware. The path, if specified, can only be a literal path to match, and is typically used for segregating middleware applications or applying rules to subsets of an diff --git a/doc/bookdown.json b/doc/bookdown.json index 9ff7395a..85a1d28d 100644 --- a/doc/bookdown.json +++ b/doc/bookdown.json @@ -14,7 +14,8 @@ {"Emitters": "book/emitters.md"}, {"Examples": "book/usage-examples.md"}, "book/cookbook/bookdown.json", - {"Expressive Projects": "book/expressive-projects.md"} + {"Expressive Projects": "book/expressive-projects.md"}, + "book/migration/bookdown.json" ], "target": "./html" } diff --git a/mkdocs.yml b/mkdocs.yml index b641106b..0a3b85ff 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,6 +15,7 @@ pages: - { Examples: usage-examples.md } - { Cookbook: [{ 'Prepending a common path to all routes': cookbook/common-prefix-for-routes.md }, { 'Route-specific middleware pipelines': cookbook/route-specific-pipeline.md }, { 'Setting custom 404 page handling': cookbook/custom-404-page-handling.md }, { 'Registering custom view helpers when using zend-view': cookbook/using-custom-view-helpers.md }, { 'Using zend-form view helpers': cookbook/using-zend-form-view-helpers.md }, { 'Using Expressive from a subdirectory': cookbook/using-a-base-path.md }, { 'Building modular applications': cookbook/modular-layout.md }, { 'Setting a locale based on a routing parameter': cookbook/setting-locale-depending-routing-parameter.md }, { 'Setting a locale without a routing parameter': cookbook/setting-locale-without-routing-parameter.md }, { 'Enabling debug toolbars': cookbook/debug-toolbars.md }, { 'Handling multiple routes in a single class': cookbook/using-routed-middleware-class-as-controller.md }] } - { 'Expressive Projects': expressive-projects.md } + - { Migration: [{ 'From RC5 and Earlier': migration/rc-to-v1.md }] } site_name: zend-expressive site_description: 'zend-expressive: PSR-7 Middleware Microframework' repo_url: 'https://github.com/zendframework/zend-expressive' diff --git a/src/Application.php b/src/Application.php index 9353e325..1449dd1d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -120,7 +120,6 @@ public function __construct( */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null) { - $this->pipeRoutingMiddleware(); $out = $out ?: $this->getFinalHandler($response); return parent::__invoke($request, $response, $out); } @@ -232,29 +231,27 @@ public function notifyRouteResultObservers(Router\RouteResult $result) * the upshot is that you will not be notified if the service is invalid to * use as middleware until runtime. * - * Additionally, ensures that the route middleware is only ever registered + * Middleware may also be passed as an array; each item in the array must + * resolve to middleware eventually (i.e., callable or service name). + * + * Finally, ensures that the route middleware is only ever registered * once. * - * @param string|callable $path Either a URI path prefix, or middleware. - * @param null|string|callable $middleware Middleware + * @param string|array|callable $path Either a URI path prefix, or middleware. + * @param null|string|array|callable $middleware Middleware * @return self */ public function pipe($path, $middleware = null) { - // Lazy-load middleware from the container when possible - $container = $this->container; - if (null === $middleware && is_string($path) && $container && $container->has($path)) { - $middleware = $this->marshalLazyMiddlewareService($path, $container); - $path = '/'; - } elseif (is_string($middleware) - && ! is_callable($middleware) - && $container - && $container->has($middleware) + if (null === $middleware) { + $middleware = $this->prepareMiddleware($path); + $path = '/'; + } + + if (! is_callable($middleware) + && (is_string($middleware) || is_array($middleware)) ) { - $middleware = $this->marshalLazyMiddlewareService($middleware, $container); - } elseif (null === $middleware && is_callable($path)) { - $middleware = $path; - $path = '/'; + $middleware = $this->prepareMiddleware($middleware); } if ($middleware === [$this, 'routeMiddleware'] && $this->routeMiddlewareIsRegistered) { @@ -303,20 +300,15 @@ public function pipe($path, $middleware = null) */ public function pipeErrorHandler($path, $middleware = null) { - // Lazy-load middleware from the container - $container = $this->container; - if (null === $middleware && is_string($path) && $container && $container->has($path)) { - $middleware = $this->marshalLazyErrorMiddlewareService($path, $container); - $path = '/'; - } elseif (is_string($middleware) - && ! is_callable($middleware) - && $container - && $container->has($middleware) + if (null === $middleware) { + $middleware = $this->prepareMiddleware($path, $forError = true); + $path = '/'; + } + + if (! is_callable($middleware) + && (is_string($middleware) || is_array($middleware)) ) { - $middleware = $this->marshalLazyErrorMiddlewareService($middleware, $container); - } elseif (null === $middleware && is_callable($path)) { - $middleware = $path; - $path = '/'; + $middleware = $this->prepareMiddleware($middleware, $forError = true); } $this->pipe($path, $middleware); @@ -382,13 +374,8 @@ public function routeMiddleware(ServerRequestInterface $request, ResponseInterfa )); } - if (is_array($middleware) && ! is_callable($middleware)) { - $middlewarePipe = $this->marshalMiddlewarePipe($middleware); - return $middlewarePipe($request, $response, $next); - } - - $callable = $this->marshalMiddleware($middleware); - return $callable($request, $response, $next); + $middleware = $this->prepareMiddleware($middleware); + return $middleware($request, $response, $next); } /** @@ -556,45 +543,54 @@ private function checkForDuplicateRoute($path, $methods = null) } /** - * Attempts to retrieve middleware from the container, or instantiate it directly. + * Prepare middleware for piping. * - * @param string $middleware + * Performs a number of checks on $middleware to prepare it for piping + * to the application: + * + * - If it's callable, it's returned immediately. + * - If it's a non-callable array, it's passed to marshalMiddlewarePipe(). + * - If it's a string service name, it's passed to marshalLazyMiddlewareService(). + * - If it's a string class name, it's passed to marshalInvokableMiddleware(). + * - If no callable is created, an exception is thrown. * + * @param mixed $middleware * @return callable - * @throws Exception\InvalidMiddlewareException If unable to obtain callable middleware + * @throws Exception\InvalidMiddlewareException */ - private function marshalMiddleware($middleware) + private function prepareMiddleware($middleware, $forError = false) { if (is_callable($middleware)) { return $middleware; } - if (! is_string($middleware)) { - throw new Exception\InvalidMiddlewareException( - 'The middleware specified is not callable' - ); + if (is_array($middleware)) { + return $this->marshalMiddlewarePipe($middleware, $forError); } - // try to get the action name from the container (if exists) - $callable = $this->marshalMiddlewareFromContainer($middleware); - - if (is_callable($callable)) { - return $callable; + $container = $this->container; + if (is_string($middleware) && $container && $container->has($middleware)) { + $method = $forError ? 'marshalLazyErrorMiddlewareService' : 'marshalLazyMiddlewareService'; + return $this->{$method}($middleware, $container); } - // try to instantiate the middleware directly, if possible - $callable = $this->marshalInvokableMiddleware($middleware); + $callable = $middleware; + if (is_string($middleware)) { + $callable = $this->marshalInvokableMiddleware($middleware); + } - if (is_callable($callable)) { - return $callable; + if (! is_callable($callable)) { + throw new Exception\InvalidMiddlewareException( + sprintf( + 'Unable to resolve middleware "%s" to a callable', + (is_object($middleware) + ? get_class($middleware) . "[Object]" + : gettype($middleware) . '[Scalar]') + ) + ); } - throw new Exception\InvalidMiddlewareException( - sprintf( - 'Unable to resolve middleware "%s" to a callable', - $middleware - ) - ); + return $callable; } /** @@ -613,44 +609,19 @@ private function marshalMiddleware($middleware) * @return MiddlewarePipe * @throws Exception\InvalidMiddlewareException for any invalid middleware items. */ - private function marshalMiddlewarePipe(array $middlewares) + private function marshalMiddlewarePipe(array $middlewares, $forError = false) { $middlewarePipe = new MiddlewarePipe(); foreach ($middlewares as $middleware) { $middlewarePipe->pipe( - $this->marshalMiddleware($middleware) + $this->prepareMiddleware($middleware, $forError) ); } return $middlewarePipe; } - /** - * Attempt to retrieve the given middleware from the container. - * - * @param string $middleware - * @return string|callable Returns $middleware intact on failure, and the - * middleware instance on success. - * @throws Exception\InvalidArgumentException if a container exception occurs. - */ - private function marshalMiddlewareFromContainer($middleware) - { - $container = $this->container; - if (! $container || ! $container->has($middleware)) { - return $middleware; - } - - try { - return $container->get($middleware); - } catch (ContainerException $e) { - throw new Exception\InvalidMiddlewareException(sprintf( - 'Unable to retrieve middleware "%s" from the container', - $middleware - ), $e->getCode(), $e); - } - } - /** * Attempt to instantiate the given middleware. * diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index 04ad8b97..57816906 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -113,6 +113,8 @@ */ class ApplicationFactory { + const ROUTING_MIDDLEWARE = 'EXPRESSIVE_ROUTING_MIDDLEWARE'; + /** * Create and return an Application instance. * @@ -138,29 +140,147 @@ public function __invoke(ContainerInterface $container) $app = new Application($router, $container, $finalHandler, $emitter); - $this->injectPreMiddleware($app, $container); - $this->injectRoutes($app, $container); - $this->injectPostMiddleware($app, $container); + $this->injectRoutesAndPipeline($app, $container); return $app; } /** - * Inject routes from configuration, if any. + * Injects routes and the middleware pipeline into the application. * * @param Application $app * @param ContainerInterface $container */ - private function injectRoutes(Application $app, ContainerInterface $container) + private function injectRoutesAndPipeline(Application $app, ContainerInterface $container) { + // This is set to true by default; injectPipeline() will set it to false if + // it injects pipeline middleware, but not the routing middleware - which + // is the only situation where we may have a problem. + $routingMiddlewareInjected = true; + $config = $container->has('config') ? $container->get('config') : []; + if (isset($config['middleware_pipeline']) && is_array($config['middleware_pipeline'])) { + $routingMiddlewareInjected = $this->injectPipeline($config['middleware_pipeline'], $app); + } + + if (isset($config['routes']) && is_array($config['routes'])) { + if (count($config['routes']) > 0 && ! $routingMiddlewareInjected) { + throw new ContainerInvalidArgumentException( + 'A middleware pipeline was defined that does not include the routing middleware, ' + . 'but routes are also defined; please add the routing middleware to your ' + . 'middleware pipeline' + ); + } + $this->injectRoutes($config['routes'], $app); + } + } + + /** + * Inject the middleware pipeline + * + * This method injects the middleware pipeline. + * + * If the pre-RC6 pre_/post_routing keys exist, it raises a deprecation + * notice, and then builds the pipeline based on that configuration + * (though it will raise an exception if other keys are *also* present). + * + * Otherwise, it passes the pipeline on to `injectMiddleware()`, + * returning a boolean value based on whether or not the routing + * middleware was injected. + * + * @deprecated This method will be removed in v1.1. + * @param array $pipeline + * @param Application $app + * @return bool + */ + private function injectPipeline(array $pipeline, Application $app) + { + $deprecatedKeys = $this->getDeprecatedKeys(array_keys($pipeline)); + if (! empty($deprecatedKeys)) { + $this->handleDeprecatedPipeline($deprecatedKeys, $pipeline, $app); + return true; + } + + return $this->injectMiddleware($pipeline, $app); + } + + /** + * Retrieve a list of deprecated keys from the pipeline, if any. + * + * @deprecated This method will be removed in v1.1. + * @param array $pipelineKeys + * @return array + */ + private function getDeprecatedKeys(array $pipelineKeys) + { + return array_intersect(['pre_routing', 'post_routing'], $pipelineKeys); + } + + /** + * Handle deprecated pre_/post_routing configuration. + * + * @deprecated This method will be removed in v1.1. + * @param array $deprecatedKeys The list of deprecated keys present in the + * pipeline + * @param array $pipeline + * @param Application $app + * @return void + * @throws ContainerInvalidArgumentException if $pipeline contains more than + * just pre_ and/or post_routing keys. + * @throws ContainerInvalidArgumentException if the pre_routing configuration, + * if present, is not an array + * @throws ContainerInvalidArgumentException if the post_routing configuration, + * if present, is not an array + */ + private function handleDeprecatedPipeline(array $deprecatedKeys, array $pipeline, Application $app) + { + if (count($deprecatedKeys) < count($pipeline)) { + throw new ContainerInvalidArgumentException( + 'middleware_pipeline cannot contain a mix of middleware AND pre_/post_routing keys; ' + . 'please update your configuration to define middleware_pipeline as a single pipeline; ' + . 'see http://zend-expressive.rtfd.org/en/latest/migration/rc-to-v1/' + ); + } - if (! isset($config['routes'])) { - $app->pipeRoutingMiddleware(); - return; + trigger_error( + 'pre_routing and post_routing configuration is deprecated; ' + . 'update your configuration to define the middleware_pipeline as a single pipeline; ' + . 'see http://zend-expressive.rtfd.org/en/latest/migration/rc-to-v1/', + E_USER_DEPRECATED + ); + + if (isset($pipeline['pre_routing'])) { + if (! is_array($pipeline['pre_routing'])) { + throw new ContainerInvalidArgumentException(sprintf( + 'Pre-routing middleware collection must be an array; received "%s"', + gettype($pipeline['pre_routing']) + )); + } + $this->injectMiddleware($pipeline['pre_routing'], $app); + } + + $app->pipeRoutingMiddleware(); + + if (isset($pipeline['post_routing'])) { + if (! is_array($pipeline['post_routing'])) { + throw new ContainerInvalidArgumentException(sprintf( + 'Post-routing middleware collection must be an array; received "%s"', + gettype($pipeline['post_routing']) + )); + } + $this->injectMiddleware($pipeline['post_routing'], $app); } + } - foreach ($config['routes'] as $spec) { + /** + * Inject routes from configuration, if any. + * + * @param array $routes Route definitions + * @param Application $app + */ + private function injectRoutes(array $routes, Application $app) + { + foreach ($routes as $spec) { if (! isset($spec['path']) || ! isset($spec['middleware'])) { continue; } @@ -200,13 +320,28 @@ private function injectRoutes(Application $app, ContainerInterface $container) * * @param array $collection * @param Application $app - * @param ContainerInterface $container + * @return int Count of middleware injected at the top-level * @throws Exception\InvalidMiddlewareException for invalid middleware. */ - private function injectMiddleware(array $collection, Application $app, ContainerInterface $container) + private function injectMiddleware(array $collection, Application $app) { + // Return true if the collection is empty, as that means no middleware + // was injected, and adding routes will not lead to an error condition. + if (empty($collection)) { + return true; + } + + $routingMiddlewareInjected = false; + $isRoutingMiddleware = function ($middleware) use ($app) { + return ([$app, 'routeMiddleware'] === $middleware); + }; + foreach ($collection as $spec) { - if (! array_key_exists('middleware', $spec)) { + if ($spec === self::ROUTING_MIDDLEWARE) { + $spec = ['middleware' => [$app, 'routeMiddleware']]; + } + + if (! is_array($spec) || ! array_key_exists('middleware', $spec)) { continue; } @@ -214,79 +349,16 @@ private function injectMiddleware(array $collection, Application $app, Container $error = array_key_exists('error', $spec) ? (bool) $spec['error'] : false; $pipe = $error ? 'pipeErrorHandler' : 'pipe'; - if (is_array($spec['middleware'])) { + $app->{$pipe}($path, $spec['middleware']); + $routingMiddlewareInjected = $routingMiddlewareInjected || $isRoutingMiddleware($spec['middleware']); + + // If it is an array of middleware, check if any were the routing middleware + if (is_array($spec['middleware']) && $pipe === 'pipe') { foreach ($spec['middleware'] as $middleware) { - $app->{$pipe}($path, $middleware); + $routingMiddlewareInjected = $routingMiddlewareInjected || $isRoutingMiddleware($middleware); } - } else { - $app->{$pipe}($path, $spec['middleware']); } } - } - - /** - * Inject middleware to pipe before the routing middleware. - * - * Pre-routing middleware is specified as the configuration subkey - * middleware_pipeline.pre_routing. - * - * @param Application $app - * @param ContainerInterface $container - */ - private function injectPreMiddleware(Application $app, ContainerInterface $container) - { - if (!$container->has('config')) { - return; - } - - $config = $container->get('config'); - - if (! isset($config['middleware_pipeline']['pre_routing'])) { - return; - } - - $middlewareCollection = $config['middleware_pipeline']['pre_routing']; - - if (! is_array($middlewareCollection)) { - throw new ContainerInvalidArgumentException(sprintf( - 'Pre-routing middleware collection must be an array; received "%s"', - gettype($middlewareCollection) - )); - } - - $this->injectMiddleware($middlewareCollection, $app, $container); - } - - /** - * Inject middleware to pipe after the routing middleware. - * - * Post-routing middleware is specified as the configuration subkey - * middleware_pipeline.post_routing. - * - * @param Application $app - * @param ContainerInterface $container - */ - private function injectPostMiddleware(Application $app, ContainerInterface $container) - { - if (!$container->has('config')) { - return; - } - - $config = $container->get('config'); - - if (! isset($config['middleware_pipeline']['post_routing'])) { - return; - } - - $middlewareCollection = $config['middleware_pipeline']['post_routing']; - - if (! is_array($middlewareCollection)) { - throw new ContainerInvalidArgumentException(sprintf( - 'Post-routing middleware collection must be an array; received "%s"', - gettype($middlewareCollection) - )); - } - - $this->injectMiddleware($middlewareCollection, $app, $container); + return $routingMiddlewareInjected; } } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 7cb31f17..7a4e17e2 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -432,50 +432,6 @@ public function testCanTriggerPipingOfRouteMiddleware() $this->assertEquals('/', $route->path); } - /** - * @group 64 - */ - public function testInvocationWillPipeRoutingMiddlewareIfNotAlreadyPiped() - { - $request = new Request([], [], 'http://example.com/'); - $response = $this->prophesize(ResponseInterface::class); - - $middleware = function ($req, $res, $next = null) { - return $res; - }; - - $this->router->match($request)->willReturn(RouteResult::fromRouteMatch('foo', 'foo', [])); - - $container = $this->mockContainerInterface(); - $this->injectServiceInContainer($container, 'foo', $middleware); - - $app = new Application($this->router->reveal(), $container->reveal()); - - $pipeline = $this->prophesize(SplQueue::class); - - // Test that the route middleware is enqueued - $pipeline->enqueue(Argument::that(function ($route) use ($app) { - if (! $route instanceof StratigilityRoute) { - return false; - } - - if ($route->path !== '/') { - return false; - } - - return ($route->handler === [$app, 'routeMiddleware']); - }))->shouldBeCalled(); - - // Prevent dequeueing - $pipeline->isEmpty()->willReturn(true); - - $r = new ReflectionProperty($app, 'pipeline'); - $r->setAccessible(true); - $r->setValue($app, $pipeline->reveal()); - - $app($request, $response->reveal(), $middleware); - } - /** * @group lazy-piping */ diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php index b6801c76..f7d13d4f 100644 --- a/test/Container/ApplicationFactoryTest.php +++ b/test/Container/ApplicationFactoryTest.php @@ -199,6 +199,7 @@ public function testWillUseSaneDefaultsForOptionalServices() /** * @group piping + * @deprecated This test can be removed for 1.1 */ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRoutes() { @@ -257,6 +258,7 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo /** * @group piping + * @deprecated This test can be removed for 1.1 */ public function testCanPipeMiddlewareProvidedDuringConfigurationAfterSettingRoutes() { @@ -327,10 +329,8 @@ public function testPipedMiddlewareAsServiceNamesAreReturnedAsClosuresThatPullFr $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'middleware' => 'Middleware' ], - [ 'path' => '/foo', 'middleware' => 'Middleware' ], - ], + [ 'middleware' => 'Middleware' ], + [ 'path' => '/foo', 'middleware' => 'Middleware' ], ], ]; @@ -343,12 +343,7 @@ public function testPipedMiddlewareAsServiceNamesAreReturnedAsClosuresThatPullFr $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(3, $pipeline); - - $route = $pipeline->dequeue(); - $this->assertInstanceOf(StratigilityRoute::class, $route); - $this->assertSame([$app, 'routeMiddleware'], $route->handler); - $this->assertEquals('/', $route->path); + $this->assertCount(2, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -370,10 +365,8 @@ public function testMiddlewareIsNotAddedIfSpecIsInvalid() { $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'foo' => 'bar' ], - [ 'path' => '/foo' ], - ], + [ 'foo' => 'bar' ], + [ 'path' => '/foo' ], ], ]; @@ -385,12 +378,7 @@ public function testMiddlewareIsNotAddedIfSpecIsInvalid() $r->setAccessible(true); $pipeline = $r->getValue($app); - // only routeMiddleware should be added by default - $this->assertCount(1, $pipeline); - $route = $pipeline->dequeue(); - $this->assertInstanceOf(StratigilityRoute::class, $route); - $this->assertSame([$app, 'routeMiddleware'], $route->handler); - $this->assertEquals('/', $route->path); + $this->assertCount(0, $pipeline); } public function uncallableMiddleware() @@ -416,21 +404,28 @@ public function testRaisesExceptionForNonCallableNonServiceMiddleware($middlewar { $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'middleware' => $middleware ], - [ 'path' => '/foo', 'middleware' => $middleware ], - ], + [ 'middleware' => $middleware ], + [ 'path' => '/foo', 'middleware' => $middleware ], ], ]; $this->injectServiceInContainer($this->container, 'config', $config); - $this->setExpectedException(InvalidArgumentException::class); - $app = $this->factory->__invoke($this->container->reveal()); + try { + $this->factory->__invoke($this->container->reveal()); + $this->fail('No exception raised when fetching non-callable non-service middleware'); + } catch (InvalidMiddlewareException $e) { + // This is acceptable + $this->assertInstanceOf(InvalidMiddlewareException::class, $e); + } catch (InvalidArgumentException $e) { + // This is acceptable + $this->assertInstanceOf(InvalidArgumentException::class, $e); + } } /** * @group piping + * @deprecated This test can be removed for 1.1 */ public function testCanPipePreRoutingMiddlewareAsArray() { @@ -464,6 +459,7 @@ function () { /** * @group piping + * @deprecated This test can be removed for 1.1 */ public function testCanPipePostRoutingMiddlewareAsArray() { @@ -485,7 +481,14 @@ function () { $this->injectServiceInContainer($this->container, 'Hello', function () { }); + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); } /** @@ -495,16 +498,14 @@ public function testRaisesExceptionForPipedMiddlewareServiceNamesNotFoundInConta { $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'middleware' => 'Middleware' ], - [ 'path' => '/foo', 'middleware' => 'Middleware' ], - ], + [ 'middleware' => 'Middleware' ], + [ 'path' => '/foo', 'middleware' => 'Middleware' ], ], ]; $this->injectServiceInContainer($this->container, 'config', $config); - $this->setExpectedException(InvalidArgumentException::class); + $this->setExpectedException(InvalidMiddlewareException::class); $app = $this->factory->__invoke($this->container->reveal()); } @@ -517,10 +518,8 @@ public function testRaisesExceptionOnInvocationOfUninvokableServiceSpecifiedMidd $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'middleware' => 'Middleware' ], - [ 'path' => '/foo', 'middleware' => 'Middleware' ], - ], + [ 'middleware' => 'Middleware' ], + [ 'path' => '/foo', 'middleware' => 'Middleware' ], ], ]; @@ -533,12 +532,7 @@ public function testRaisesExceptionOnInvocationOfUninvokableServiceSpecifiedMidd $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(3, $pipeline); - - $routing = $pipeline->dequeue(); - $this->assertInstanceOf(StratigilityRoute::class, $routing); - $this->assertSame([$app, 'routeMiddleware'], $routing->handler); - $this->assertEquals('/', $routing->path); + $this->assertCount(2, $pipeline); $first = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $first); @@ -596,9 +590,7 @@ public function testCanMarkPipedMiddlewareServiceAsErrorMiddleware() $config = [ 'middleware_pipeline' => [ - 'post_routing' => [ - [ 'middleware' => 'Middleware', 'error' => true ], - ], + [ 'middleware' => 'Middleware', 'error' => true ], ], ]; @@ -611,12 +603,7 @@ public function testCanMarkPipedMiddlewareServiceAsErrorMiddleware() $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(2, $pipeline); - - $route = $pipeline->dequeue(); - $this->assertInstanceOf(StratigilityRoute::class, $route); - $this->assertEquals('/', $route->path); - $this->assertSame([$app, 'routeMiddleware'], $route->handler); + $this->assertCount(1, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -630,6 +617,7 @@ public function testCanMarkPipedMiddlewareServiceAsErrorMiddleware() /** * @group 64 + * @deprecated This test can be removed for 1.1 */ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() { @@ -781,6 +769,9 @@ public function testExceptionIsRaisedInCaseOfInvalidRouteOptionsConfiguration() $this->factory->__invoke($this->container->reveal()); } + /** + * @deprecated This test can be removed for 1.1 + */ public function testExceptionIsRaisedInCaseOfInvalidPreRoutingMiddlewarePipeline() { $config = [ @@ -804,6 +795,9 @@ public function testExceptionIsRaisedInCaseOfInvalidPreRoutingMiddlewarePipeline $this->factory->__invoke($this->container->reveal()); } + /** + * @deprecated This test can be removed for 1.1 + */ public function testExceptionIsRaisedInCaseOfInvalidPostRoutingMiddlewarePipeline() { $config = [ @@ -818,16 +812,13 @@ public function testExceptionIsRaisedInCaseOfInvalidPostRoutingMiddlewarePipelin ContainerException\InvalidArgumentException::class, 'Post-routing middleware collection must be an array; received "string"' ); - $this->factory->__invoke($this->container->reveal()); - } - - public function middlewarePipelines() - { // @codingStandardsIgnoreStart - return [ - - ]; + set_error_handler(function ($errno, $errmsg) { + $this->assertContains('routing', $errmsg); + }, E_USER_DEPRECATED); // @codingStandardsIgnoreEnd + + $this->factory->__invoke($this->container->reveal()); } public function testWillCreatePipelineBasedOnMiddlewareConfiguration() @@ -901,6 +892,10 @@ public function testWillCreatePipelineBasedOnMiddlewareConfiguration() $nestedPipeline = $test->handler; $this->assertInstanceOf(MiddlewarePipe::class, $nestedPipeline); + $r = new ReflectionProperty($nestedPipeline, 'pipeline'); + $r->setAccessible(true); + $nestedPipeline = $r->getValue($nestedPipeline); + $test = $nestedPipeline->dequeue(); $this->assertSame($pipelineFirst, $test->handler); @@ -953,6 +948,7 @@ public function middlewarePipelinesWithPreOrPostRouting() /** * @dataProvider middlewarePipelinesWithPreOrPostRouting + * @deprecated This test can be removed for 1.1 */ public function testRaisesDeprecationNoticeForUsageOfPreOrPostRoutingPipelineConfiguration($config) { @@ -963,6 +959,7 @@ public function testRaisesDeprecationNoticeForUsageOfPreOrPostRoutingPipelineCon $triggered = false; set_error_handler(function ($errno, $errmsg) use (&$triggered) { $this->assertContains('routing', $errmsg); + $triggered = true; }, E_USER_DEPRECATED); // @codingStandardsIgnoreEnd @@ -1034,4 +1031,17 @@ public function testProvidingRoutesAndNoPipelineImplicitlyRegistersRoutingMiddle $this->assertEquals('/', $test->path); $this->assertSame([$app, 'routeMiddleware'], $test->handler); } + + public function testPipelineContainingRoutingMiddlewareConstantPipesRoutingMiddleware() + { + $config = [ + 'middleware_pipeline' => [ + ApplicationFactory::ROUTING_MIDDLEWARE, + ], + ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + $this->assertAttributeSame(true, 'routeMiddlewareIsRegistered', $app); + } } diff --git a/test/RouteMiddlewareTest.php b/test/RouteMiddlewareTest.php index 2972d740..faae53bd 100644 --- a/test/RouteMiddlewareTest.php +++ b/test/RouteMiddlewareTest.php @@ -245,32 +245,6 @@ public function testRoutingSuccessResolvingToContainerMiddlewareCallsIt() $this->assertEquals('Invoked', $test->getHeaderLine('X-Middleware')); } - public function testRoutingSuccessResultingInContainerExceptionReRaisesException() - { - $request = new ServerRequest(); - $response = new Response(); - - $result = RouteResult::fromRouteMatch( - '/foo', - 'TestAsset\Middleware', - [] - ); - - $this->router->match($request)->willReturn($result); - - $this->container->has('TestAsset\Middleware')->willReturn(true); - $this->container->get('TestAsset\Middleware')->willThrow(new TestAsset\ContainerException()); - - $app = $this->getApplication(); - - $next = function ($request, $response) { - $this->fail('Should not enter $next'); - }; - - $this->setExpectedException(InvalidMiddlewareException::class, 'retrieve'); - $app->routeMiddleware($request, $response, $next); - } - /** * Get the router adapters to test */ From dd372bece17f3b99636739edda22644076ab55b0 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 11:07:12 -0600 Subject: [PATCH 03/18] Fixed CS issues --- src/Container/ApplicationFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index 57816906..e33e90d5 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -260,7 +260,7 @@ private function handleDeprecatedPipeline(array $deprecatedKeys, array $pipeline } $app->pipeRoutingMiddleware(); - + if (isset($pipeline['post_routing'])) { if (! is_array($pipeline['post_routing'])) { throw new ContainerInvalidArgumentException(sprintf( From 68047b697471bc3471e7e5b3e681725d30305337 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 13:03:43 -0600 Subject: [PATCH 04/18] Moved middleware marshaling methods to a trait - Allows re-use for alternate routing/dispatching implementations. - Reduces immediate LOC in Application class. --- src/Application.php | 146 ++---------------------------- src/MarshalMiddlewareTrait.php | 159 +++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 139 deletions(-) create mode 100644 src/MarshalMiddlewareTrait.php diff --git a/src/Application.php b/src/Application.php index 1449dd1d..c4b8dd60 100644 --- a/src/Application.php +++ b/src/Application.php @@ -31,6 +31,8 @@ */ class Application extends MiddlewarePipe implements Router\RouteResultSubjectInterface { + use MarshalMiddlewareTrait; + /** * @var null|ContainerInterface */ @@ -244,14 +246,14 @@ public function notifyRouteResultObservers(Router\RouteResult $result) public function pipe($path, $middleware = null) { if (null === $middleware) { - $middleware = $this->prepareMiddleware($path); + $middleware = $this->prepareMiddleware($path, $this->container); $path = '/'; } if (! is_callable($middleware) && (is_string($middleware) || is_array($middleware)) ) { - $middleware = $this->prepareMiddleware($middleware); + $middleware = $this->prepareMiddleware($middleware, $this->container); } if ($middleware === [$this, 'routeMiddleware'] && $this->routeMiddlewareIsRegistered) { @@ -301,14 +303,14 @@ public function pipe($path, $middleware = null) public function pipeErrorHandler($path, $middleware = null) { if (null === $middleware) { - $middleware = $this->prepareMiddleware($path, $forError = true); + $middleware = $this->prepareMiddleware($path, $this->container, $forError = true); $path = '/'; } if (! is_callable($middleware) && (is_string($middleware) || is_array($middleware)) ) { - $middleware = $this->prepareMiddleware($middleware, $forError = true); + $middleware = $this->prepareMiddleware($middleware, $this->container, $forError = true); } $this->pipe($path, $middleware); @@ -374,7 +376,7 @@ public function routeMiddleware(ServerRequestInterface $request, ResponseInterfa )); } - $middleware = $this->prepareMiddleware($middleware); + $middleware = $this->prepareMiddleware($middleware, $this->container); return $middleware($request, $response, $next); } @@ -541,138 +543,4 @@ private function checkForDuplicateRoute($path, $methods = null) ); } } - - /** - * Prepare middleware for piping. - * - * Performs a number of checks on $middleware to prepare it for piping - * to the application: - * - * - If it's callable, it's returned immediately. - * - If it's a non-callable array, it's passed to marshalMiddlewarePipe(). - * - If it's a string service name, it's passed to marshalLazyMiddlewareService(). - * - If it's a string class name, it's passed to marshalInvokableMiddleware(). - * - If no callable is created, an exception is thrown. - * - * @param mixed $middleware - * @return callable - * @throws Exception\InvalidMiddlewareException - */ - private function prepareMiddleware($middleware, $forError = false) - { - if (is_callable($middleware)) { - return $middleware; - } - - if (is_array($middleware)) { - return $this->marshalMiddlewarePipe($middleware, $forError); - } - - $container = $this->container; - if (is_string($middleware) && $container && $container->has($middleware)) { - $method = $forError ? 'marshalLazyErrorMiddlewareService' : 'marshalLazyMiddlewareService'; - return $this->{$method}($middleware, $container); - } - - $callable = $middleware; - if (is_string($middleware)) { - $callable = $this->marshalInvokableMiddleware($middleware); - } - - if (! is_callable($callable)) { - throw new Exception\InvalidMiddlewareException( - sprintf( - 'Unable to resolve middleware "%s" to a callable', - (is_object($middleware) - ? get_class($middleware) . "[Object]" - : gettype($middleware) . '[Scalar]') - ) - ); - } - - return $callable; - } - - /** - * Marshal a middleware pipe from an array of middleware. - * - * Each item in the array can be one of the following: - * - * - A callable middleware - * - A string service name of middleware to retrieve from the container - * - A string class name of a constructor-less middleware class to - * instantiate - * - * As each middleware is verified, it is piped to the middleware pipe. - * - * @param array $middlewares - * @return MiddlewarePipe - * @throws Exception\InvalidMiddlewareException for any invalid middleware items. - */ - private function marshalMiddlewarePipe(array $middlewares, $forError = false) - { - $middlewarePipe = new MiddlewarePipe(); - - foreach ($middlewares as $middleware) { - $middlewarePipe->pipe( - $this->prepareMiddleware($middleware, $forError) - ); - } - - return $middlewarePipe; - } - - /** - * Attempt to instantiate the given middleware. - * - * @param string $middleware - * @return string|callable Returns $middleware intact on failure, and the - * middleware instance on success. - */ - private function marshalInvokableMiddleware($middleware) - { - if (! class_exists($middleware)) { - return $middleware; - } - - return new $middleware(); - } - - /** - * @param string $middleware - * @param ContainerInterface $container - * @return callable - */ - private function marshalLazyMiddlewareService($middleware, ContainerInterface $container) - { - return function ($request, $response, $next = null) use ($container, $middleware) { - $invokable = $container->get($middleware); - if (! is_callable($invokable)) { - throw new Exception\InvalidMiddlewareException(sprintf( - 'Lazy-loaded middleware "%s" is not invokable', - $middleware - )); - } - return $invokable($request, $response, $next); - }; - } - - /** - * @param string $middleware - * @param ContainerInterface $container - * @return callable - */ - private function marshalLazyErrorMiddlewareService($middleware, ContainerInterface $container) - { - return function ($error, $request, $response, $next) use ($container, $middleware) { - $invokable = $container->get($middleware); - if (! is_callable($invokable)) { - throw new Exception\InvalidMiddlewareException(sprintf( - 'Lazy-loaded middleware "%s" is not invokable', - $middleware - )); - } - return $invokable($error, $request, $response, $next); - }; - } } diff --git a/src/MarshalMiddlewareTrait.php b/src/MarshalMiddlewareTrait.php new file mode 100644 index 00000000..64323cca --- /dev/null +++ b/src/MarshalMiddlewareTrait.php @@ -0,0 +1,159 @@ +marshalMiddlewarePipe($middleware, $container, $forError); + } + + if (is_string($middleware) && $container && $container->has($middleware)) { + $method = $forError ? 'marshalLazyErrorMiddlewareService' : 'marshalLazyMiddlewareService'; + return $this->{$method}($middleware, $container); + } + + $callable = $middleware; + if (is_string($middleware)) { + $callable = $this->marshalInvokableMiddleware($middleware); + } + + if (! is_callable($callable)) { + throw new Exception\InvalidMiddlewareException( + sprintf( + 'Unable to resolve middleware "%s" to a callable', + (is_object($middleware) + ? get_class($middleware) . "[Object]" + : gettype($middleware) . '[Scalar]') + ) + ); + } + + return $callable; + } + + /** + * Marshal a middleware pipe from an array of middleware. + * + * Each item in the array can be one of the following: + * + * - A callable middleware + * - A string service name of middleware to retrieve from the container + * - A string class name of a constructor-less middleware class to + * instantiate + * + * As each middleware is verified, it is piped to the middleware pipe. + * + * @param array $middlewares + * @param null|ContainerInterface $container + * @param bool $forError Whether or not the middleware pipe generated is + * intended to be populated with error middleware; defaults to false. + * @return MiddlewarePipe + * @throws Exception\InvalidMiddlewareException for any invalid middleware items. + */ + private function marshalMiddlewarePipe(array $middlewares, ContainerInterface $container = null, $forError = false) + { + $middlewarePipe = new MiddlewarePipe(); + + foreach ($middlewares as $middleware) { + $middlewarePipe->pipe( + $this->prepareMiddleware($middleware, $container, $forError) + ); + } + + return $middlewarePipe; + } + + /** + * Attempt to instantiate the given middleware. + * + * @param string $middleware + * @return string|callable Returns $middleware intact on failure, and the + * middleware instance on success. + */ + private function marshalInvokableMiddleware($middleware) + { + if (! class_exists($middleware)) { + return $middleware; + } + + return new $middleware(); + } + + /** + * @param string $middleware + * @param ContainerInterface $container + * @return callable + */ + private function marshalLazyMiddlewareService($middleware, ContainerInterface $container) + { + return function ($request, $response, $next = null) use ($container, $middleware) { + $invokable = $container->get($middleware); + if (! is_callable($invokable)) { + throw new Exception\InvalidMiddlewareException(sprintf( + 'Lazy-loaded middleware "%s" is not invokable', + $middleware + )); + } + return $invokable($request, $response, $next); + }; + } + + /** + * @param string $middleware + * @param ContainerInterface $container + * @return callable + */ + private function marshalLazyErrorMiddlewareService($middleware, ContainerInterface $container) + { + return function ($error, $request, $response, $next) use ($container, $middleware) { + $invokable = $container->get($middleware); + if (! is_callable($invokable)) { + throw new Exception\InvalidMiddlewareException(sprintf( + 'Lazy-loaded middleware "%s" is not invokable', + $middleware + )); + } + return $invokable($error, $request, $response, $next); + }; + } +} From dcbcd09b55b935c1f7b3debd1c49c64c6d764c4c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 14:36:59 -0600 Subject: [PATCH 05/18] Separate routing/dispatch middleware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch addresses #259 and comments from @danizord, separating `routeMiddleware()` into two distinct methods, `routeMiddleware()` and `dispatchMiddleware()`. This change also means we can no longer auto-register the routing middleware with the application. However, it enables new use cases, such as middleware interceptors between routing and dispatch, as well as substituting alternate routing and/or dispatch middleware in the application. Because the application no longer auto-registers the routing/dispatch middleware, the `ApplicationFactory` was updated to no longer raise an exception in situations where routes are registered but the routing middleware is not piped — since alternate routing middleware may be taking that responsibility. --- doc/book/application.md | 19 ++- doc/book/cookbook/custom-404-page-handling.md | 4 +- doc/book/migration/rc-to-v1.md | 137 ++++++++++++++++-- src/Application.php | 72 ++++++++- src/Container/ApplicationFactory.php | 74 ++++------ test/ApplicationTest.php | 71 +++++---- test/Container/ApplicationFactoryTest.php | 75 ++++++---- 7 files changed, 315 insertions(+), 137 deletions(-) diff --git a/doc/book/application.md b/doc/book/application.md index 0a6b8714..1a423d3b 100644 --- a/doc/book/application.md +++ b/doc/book/application.md @@ -186,21 +186,24 @@ function ($error, ServerRequestInterface $request, ResponseInterface $response, Read the section on [piping vs routing](router/piping.md) for more information. -### Registering routing middleware +### Registering routing and dispatch middleware -Routing is accomplished via dedicated a dedicated middleware method, -`Application::routeMiddleware()`. It is an instance method, and can be -piped/registered with other middleware platforms if desired. +Routing is accomplished via dedicated a dedicated middleware method, +`Application::routeMiddleware()`; similarly, dispatching of routed middleware +has a corresponding instance middleware method, `Application::dispatchMiddleware()`. +Each can be piped/registered with other middleware platforms if desired. -Internally, the first time `route()` is called (including via one of the proxy -methods), or, if never called, when `__invoke()` (the exposed application -middleware) is called, the instance will pipe `Application::routeMiddleware` to -the middleware pipeline. You can also pipe it manually if desired: +These methods **MUST** be piped to the application so that the application will +route and dispatch routed middleware. This is done using the following methods: ```php $app->pipeRoutingMiddleware(); +$app->pipeDispatchMiddleware(); ``` +See the section on [piping](router/piping.md) to see how you can register +non-routed middleware and create layered middleware applications. + ## Retrieving dependencies As noted in the intro, the `Application` class has several dependencies. Some of diff --git a/doc/book/cookbook/custom-404-page-handling.md b/doc/book/cookbook/custom-404-page-handling.md index 7b56b090..d5561f74 100644 --- a/doc/book/cookbook/custom-404-page-handling.md +++ b/doc/book/cookbook/custom-404-page-handling.md @@ -112,9 +112,7 @@ $app->pipe($container->get('Application\NotFound')); This must be done *after*: -- calling `$app->pipeRoutingMiddleware()`, **OR** -- calling any method that injects routed middleware (`get()`, `post()`, - `route()`, etc.), **OR** +- calling `$app->pipeDispatchMiddleware()`, **OR** - pulling the `Application` instance from the service container (assuming you used the `ApplicationFactory`). diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index ac394fc9..16121595 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -1,14 +1,109 @@ # Migration from RC5 or earlier -RC6 provides a breaking change intended to simplify the creation of the -middleware pipeline and to allow replacing the routing middleware. Doing so, -however, required a change to the `middleware_pipeline` configuration. +RC6 introduced changes to the following: -The changes are done in such a way as to honor configuration from RC5 and -earlier, but using such configuration will now prompt you to update your -application. This document describes how to do so. +- The routing middleware was split into separate middleware, one for routing, + and one for dispatching. +- Due to the above change, we decided to remove auto-registration of routing + middleware. +- The above change also suggested an alternative to the middleware pipeline + configuration that simplifies it. -## Configuration for RC5 and earlier +## Routing and Dispatch middleware + +Prior to RC6, the routing middleware: + +- performed routing +- notified route result observers +- created a new request that composed the matched routing parameters as request + attributes, and composed the route result instance itself as a request + attribute. +- marshaled the middleware matched by routing +- dispatched the marshaled middleware + +To provide a better separation of concerns, we split the routing middleware into +two distinct methods: `routingMiddleware()` and `dispatchMiddleware()`. + +`routingMiddleware()` performs the following duties: + +- routing; and +- creating a new request that composes the matched routing parameters as request + attributes, and composes the route result instance itself as a request + attribute. + +`dispatchMiddleware()` performs the following duties: + +- marshaling the middleware specified in the route result; and +- dispatching the marshaled middleware. + +One reason for this split is to allow injecting middleware to operate between +routing and dispatch. As an example, you could have middleware that determines +if a matched route requires an authenticated identity: + +```php +public function __invoke($request, $response, $next) +{ + $result = $request->getAttribute(RouteResult::class); + if (! in_array($result->getMatchedRouteName(), $this->authRequired)) { + return $next($request, $response); + } + + if (! $this->authenticated) { + return $next($request, $response->withStatus(401), 'authentication + required'); + } +} +``` + +The above could then be piped between the routing and dispatch middleware: + +```php +$app->pipeRoutingMiddleware(); +$app->pipe(AuthenticationMiddleware::class); +$app->pipeDispatchMiddleware(); +``` + +Since the routing middleware has been split, we determined we could no longer +automatically pipe the routing middleware; detection would require detecting +both sets of middleware, and ensuring they are in the correct order. +Additionally, since one goal of splitting the middleware is to allow +*substitutions* for these responsibilities, auto-injection could in some cases +be undesired. As a result, we now require you to inject each manually. + +### Impact + +This change will require changes in your application. + +1. If you are using Expressive programmatically (i.e., you are not using + a container and the `Zend\Expressive\Container\ApplicationFactory`), + you are now *required* to call `Application::pipeRoutingMiddleware()`. + Additionally, a new method, `Application::pipeDispatchMiddleware()` exists + for injecting the application with the dispatch middleware, this, too, must + be called. + + This has a fortunate side effect: registering routed middleware no longer + affects the middleware pipeline order. As such, you can register your + pipeline first or last prior to running the application. The only stipulation + is that _unless you register the routing **and** dispatch middleware, your routed + middleware will not be executed!_ As such, the following two lines **must** + be added to your application prior to calling `Application::run()`: + +```php +$app->pipeRoutingMiddleware(); +$app->pipeDispatchMiddleware(); +``` + +2. If you are creating your `Application` instance using a container and the + `Zend\Expressive\Container\ApplicationFactory`, you will need to update your + configuration to list the routing and dispatch middleware. The next section + details the configuration changes necessary. + +## ApplicationFactory configuration changes + +As noted in the document summary, the middleware pipeline configuration was +changed starting in RC6. The changes are done in such a way as to honor +configuration from RC5 and earlier, but using such configuration will now prompt +you to update your application. RC5 and earlier defined the default `middleware_pipeline` configuration as follows: @@ -48,7 +143,11 @@ return [ ] ``` -## Configuration for RC6 and above +### Impact + +While the configuration from RC5 and earlier will continue to work, it will +raise deprecation notices. As such, you will need to update your configuration +to follow the guidelines created with RC6. RC6 and later change the configuration to remove the `pre_routing` and `post_routing` keys. However, individual items within the array retain the same @@ -64,14 +163,17 @@ format as middleware inside those keys, namely: ] ``` -Additionally, the routing middleware itself now becomes one item in the array, -and is **required** if you have defined any routes. This change gives you full -control over the flow of the pipeline. +Additionally, the routing and dispatch middleware now become items in the array; +they (or equivalent entries for your own implementations) must be present in +your configuration if you want your routed middleware to dispatch! This change +gives you full control over the flow of the pipeline. To specify the routing middleware, use the constant `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` in place of a middleware array; this has the value `EXPRESSIVE_ROUTING_MIDDLEWARE`, if you -do not want to import the class. +do not want to import the class. Similarly, for the dispatch middleware, use the +constant `Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE` +(value `EXPRESSIVE_DISPATCH_MIDDLEWARE`) to specify the dispatch middleware. As such, the default configuration now becomes: @@ -97,7 +199,11 @@ return [ // The following is an entry for the routing middleware: Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - // Place error handling middleware after the routing middleware. + // The following is an entry for the dispatch middleware: + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + + // Place error handling middleware after the routing and dispatch + // middleware. ], ] ``` @@ -106,7 +212,8 @@ To update an existing application: - Promote all `pre_routing` middleware up a level, and remove the `pre_routing` key. -- Add the entry for `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` +- Add the entries for `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` + and `Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE` immediately following any `pre_routing` middleware, and before any `post_routing` middleware. - Promote all `post_routing` middleware up a level, and remove the @@ -115,7 +222,7 @@ To update an existing application: Once you have made the above changes, you should no longer receive deprecation notices when running your application. -## Timeline +## Timeline for migration Support for the `pre_routing` and `post_routing` configuration will be removed with the 1.1.0 release. diff --git a/src/Application.php b/src/Application.php index c4b8dd60..7a026a22 100644 --- a/src/Application.php +++ b/src/Application.php @@ -38,6 +38,12 @@ class Application extends MiddlewarePipe implements Router\RouteResultSubjectInt */ private $container; + /** + * @var bool Flag indicating whether or not the dispatch middleware is + * registered in the middleware pipeline. + */ + private $dispatchMiddlewareIsRegistered = false; + /** * @var EmitterInterface */ @@ -260,12 +266,20 @@ public function pipe($path, $middleware = null) return $this; } + if ($middleware === [$this, 'dispatchMiddleware'] && $this->dispatchMiddlewareIsRegistered) { + return $this; + } + parent::pipe($path, $middleware); if ($middleware === [$this, 'routeMiddleware']) { $this->routeMiddlewareIsRegistered = true; } + if ($middleware === [$this, 'dispatchMiddleware']) { + $this->dispatchMiddlewareIsRegistered = true; + } + return $this; } @@ -329,11 +343,28 @@ public function pipeRoutingMiddleware() $this->pipe([$this, 'routeMiddleware']); } + /** + * Register the dispatch middleware in the middleware pipeline. + */ + public function pipeDispatchMiddleware() + { + if ($this->dispatchMiddlewareIsRegistered) { + return; + } + $this->pipe([$this, 'dispatchMiddleware']); + } + /** * Middleware that routes the incoming request and delegates to the matched middleware. * - * Uses the router to route the incoming request, dispatching matched - * middleware on a request success condition. + * Uses the router to route the incoming request, injecting the request + * with: + * + * - the route result object (under a key named for the RouteResult class) + * - attributes for each matched routing parameter + * + * On completion, it calls on the next middleware (typically the + * `dispatchMiddleware()`). * * If routing fails, `$next()` is called; if routing fails due to HTTP * method negotiation, the response is set to a 405, injected with an @@ -344,9 +375,6 @@ public function pipeRoutingMiddleware() * @param ResponseInterface $response * @param callable $next * @return ResponseInterface - * @throws Exception\InvalidArgumentException if the route result does not contain middleware - * @throws Exception\InvalidArgumentException if unable to retrieve middleware from the container - * @throws Exception\InvalidArgumentException if unable to resolve middleware to a callable */ public function routeMiddleware(ServerRequestInterface $request, ResponseInterface $response, callable $next) { @@ -368,11 +396,40 @@ public function routeMiddleware(ServerRequestInterface $request, ResponseInterfa $request = $request->withAttribute($param, $value); } - $middleware = $result->getMatchedMiddleware(); + return $next($request, $response); + } + + /** + * Dispatch the middleware matched by routing. + * + * If the request does not have the route result, calls on the next + * middleware. + * + * Next, it checks if the route result has matched middleware; if not, it + * raises an exception. + * + * Finally, it attempts to marshal the middleware, and dispatches it when + * complete, return the response. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callable $next + * @returns ResponseInterface + * @throws Exception\InvalidMiddlewareException if no middleware is present + * to dispatch in the route result. + */ + public function dispatchMiddleware(ServerRequestInterface $request, ResponseInterface $response, callable $next) + { + $routeResult = $request->getAttribute(Router\RouteResult::class, false); + if (! $routeResult) { + return $next($request, $response); + } + + $middleware = $routeResult->getMatchedMiddleware(); if (! $middleware) { throw new Exception\InvalidMiddlewareException(sprintf( 'The route %s does not have a middleware to dispatch', - $result->getMatchedRouteName() + $routeResult->getMatchedRouteName() )); } @@ -421,7 +478,6 @@ public function route($path, $middleware = null, array $methods = null, $name = $this->routes[] = $route; $this->router->addRoute($route); - $this->pipeRoutingMiddleware(); return $route; } diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index e33e90d5..aefa1c86 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -72,20 +72,17 @@ * * return [ * 'middleware_pipeline' => [ - * // An array of middleware to register prior to registration of the - * // routing middleware: - * 'pre_routing' => [ - * ], - * // An array of middleware to register after registration of the - * // routing middleware: - * 'post_routing' => [ - * ], + * // An array of middleware to register with the pipeline. + * // entries to register prior to routing/dispatching... + * Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + * Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + * // entries to register after routing/dispatching... * ], * ]; * * - * Each item in either the `pre_routing` or `post_routing` array must be an - * array with the following specification: + * Each item in the middleware_pipeline array (with the exception of the routing + * and dispatch middleware entries) must be of the following specification: * * * [ @@ -104,8 +101,7 @@ * indicating the middleware is standard middleware. * * Middleware are pipe()'d to the application instance in the order in which - * they appear. "pre_routing" middleware will execute before the application's - * routing middleware, while "post_routing" middleware will execute afterwards. + * they appear. * * Middleware piped may be either callables or service names. If you specify * the middleware's `error` flag as `true`, the middleware will be piped using @@ -113,6 +109,7 @@ */ class ApplicationFactory { + const DISPATCH_MIDDLEWARE = 'EXPRESSIVE_DISPATCH_MIDDLEWARE'; const ROUTING_MIDDLEWARE = 'EXPRESSIVE_ROUTING_MIDDLEWARE'; /** @@ -153,25 +150,20 @@ public function __invoke(ContainerInterface $container) */ private function injectRoutesAndPipeline(Application $app, ContainerInterface $container) { - // This is set to true by default; injectPipeline() will set it to false if - // it injects pipeline middleware, but not the routing middleware - which - // is the only situation where we may have a problem. - $routingMiddlewareInjected = true; - $config = $container->has('config') ? $container->get('config') : []; + $pipelineCreated = false; + if (isset($config['middleware_pipeline']) && is_array($config['middleware_pipeline'])) { - $routingMiddlewareInjected = $this->injectPipeline($config['middleware_pipeline'], $app); + $pipelineCreated = $this->injectPipeline($config['middleware_pipeline'], $app); } if (isset($config['routes']) && is_array($config['routes'])) { - if (count($config['routes']) > 0 && ! $routingMiddlewareInjected) { - throw new ContainerInvalidArgumentException( - 'A middleware pipeline was defined that does not include the routing middleware, ' - . 'but routes are also defined; please add the routing middleware to your ' - . 'middleware pipeline' - ); - } $this->injectRoutes($config['routes'], $app); + + if (! $pipelineCreated) { + $app->pipeRoutingMiddleware(); + $app->pipeDispatchMiddleware(); + } } } @@ -185,7 +177,7 @@ private function injectRoutesAndPipeline(Application $app, ContainerInterface $c * (though it will raise an exception if other keys are *also* present). * * Otherwise, it passes the pipeline on to `injectMiddleware()`, - * returning a boolean value based on whether or not the routing + * returning a boolean value based on whether or not any * middleware was injected. * * @deprecated This method will be removed in v1.1. @@ -260,6 +252,7 @@ private function handleDeprecatedPipeline(array $deprecatedKeys, array $pipeline } $app->pipeRoutingMiddleware(); + $app->pipeDispatchMiddleware(); if (isset($pipeline['post_routing'])) { if (! is_array($pipeline['post_routing'])) { @@ -320,27 +313,21 @@ private function injectRoutes(array $routes, Application $app) * * @param array $collection * @param Application $app - * @return int Count of middleware injected at the top-level + * @return bool Flag indicating whether or not any middleware was injected. * @throws Exception\InvalidMiddlewareException for invalid middleware. */ private function injectMiddleware(array $collection, Application $app) { - // Return true if the collection is empty, as that means no middleware - // was injected, and adding routes will not lead to an error condition. - if (empty($collection)) { - return true; - } - - $routingMiddlewareInjected = false; - $isRoutingMiddleware = function ($middleware) use ($app) { - return ([$app, 'routeMiddleware'] === $middleware); - }; - + $injections = false; foreach ($collection as $spec) { if ($spec === self::ROUTING_MIDDLEWARE) { $spec = ['middleware' => [$app, 'routeMiddleware']]; } + if ($spec === self::DISPATCH_MIDDLEWARE) { + $spec = ['middleware' => [$app, 'dispatchMiddleware']]; + } + if (! is_array($spec) || ! array_key_exists('middleware', $spec)) { continue; } @@ -350,15 +337,8 @@ private function injectMiddleware(array $collection, Application $app) $pipe = $error ? 'pipeErrorHandler' : 'pipe'; $app->{$pipe}($path, $spec['middleware']); - $routingMiddlewareInjected = $routingMiddlewareInjected || $isRoutingMiddleware($spec['middleware']); - - // If it is an array of middleware, check if any were the routing middleware - if (is_array($spec['middleware']) && $pipe === 'pipe') { - foreach ($spec['middleware'] as $middleware) { - $routingMiddlewareInjected = $routingMiddlewareInjected || $isRoutingMiddleware($middleware); - } - } + $injections = true; } - return $routingMiddlewareInjected; + return $injections; } } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 7a4e17e2..1c073a4d 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -213,7 +213,7 @@ public function testCreatingHttpRouteWithExistingPathAndMethodRaisesException() }); } - public function testRouteMiddlewareIsNotPipedAtInstantation() + public function testRouteAndDispatchMiddlewareAreNotPipedAtInstantation() { $app = $this->getApp(); @@ -224,27 +224,7 @@ public function testRouteMiddlewareIsNotPipedAtInstantation() $this->assertCount(0, $pipeline); } - public function testRouteMiddlewareIsPipedAtFirstCallToRoute() - { - $this->router->addRoute(Argument::type(Route::class))->shouldBeCalled(); - - $app = $this->getApp(); - $app->route('/foo', 'bar'); - - $r = new ReflectionProperty($app, 'pipeline'); - $r->setAccessible(true); - $pipeline = $r->getValue($app); - - $this->assertCount(1, $pipeline); - $route = $pipeline->dequeue(); - $this->assertInstanceOf(StratigilityRoute::class, $route); - $test = $route->handler; - - $routeMiddleware = [$app, 'routeMiddleware']; - $this->assertSame($routeMiddleware, $test); - } - - public function testRouteMiddlewareCanRouteArrayOfMiddlewareAsMiddlewarePipe() + public function testDispatchMiddlewareCanDispatchArrayOfMiddlewareAsMiddlewarePipe() { $middleware = [ function () { @@ -256,14 +236,14 @@ function () { $request = new ServerRequest([], [], '/', 'GET'); $routeResult = RouteResult::fromRouteMatch(__METHOD__, $middleware, []); - $this->router->match($request)->willReturn($routeResult); + $request = $request->withAttribute(RouteResult::class, $routeResult); $container = $this->mockContainerInterface(); $this->injectServiceInContainer($container, 'FooBar', function () { }); $app = new Application($this->router->reveal(), $container->reveal()); - $app->routeMiddleware($request, new Response(), function () { + $app->dispatchMiddleware($request, new Response(), function () { }); } @@ -279,13 +259,13 @@ public function uncallableMiddleware() * @dataProvider uncallableMiddleware * @expectedException \Zend\Expressive\Exception\InvalidMiddlewareException */ - public function testThrowsExceptionWhenRoutingUncallableMiddleware($middleware) + public function testThrowsExceptionWhenDispatchingUncallableMiddleware($middleware) { $request = new ServerRequest([], [], '/', 'GET'); $routeResult = RouteResult::fromRouteMatch(__METHOD__, $middleware, []); - $this->router->match($request)->willReturn($routeResult); + $request = $request->withAttribute(RouteResult::class, $routeResult); - $this->getApp()->routeMiddleware($request, new Response(), function () { + $this->getApp()->dispatchMiddleware($request, new Response(), function () { }); } @@ -309,6 +289,26 @@ public function testCannotPipeRouteMiddlewareMoreThanOnce() $this->assertSame($routeMiddleware, $test); } + public function testCannotPipeDispatchMiddlewareMoreThanOnce() + { + $app = $this->getApp(); + $dispatchMiddleware = [$app, 'dispatchMiddleware']; + + $app->pipe($dispatchMiddleware); + $app->pipe($dispatchMiddleware); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertCount(1, $pipeline); + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $test = $route->handler; + + $this->assertSame($dispatchMiddleware, $test); + } + public function testCanInjectFinalHandlerViaConstructor() { $finalHandler = function ($req, $res, $err = null) { @@ -432,6 +432,23 @@ public function testCanTriggerPipingOfRouteMiddleware() $this->assertEquals('/', $route->path); } + public function testCanTriggerPipingOfDispatchMiddleware() + { + $app = $this->getApp(); + $app->pipeDispatchMiddleware(); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertCount(1, $pipeline); + + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); + } + /** * @group lazy-piping */ diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php index f7d13d4f..1e229840 100644 --- a/test/Container/ApplicationFactoryTest.php +++ b/test/Container/ApplicationFactoryTest.php @@ -238,7 +238,7 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(3, $pipeline); + $this->assertCount(4, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -254,6 +254,11 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); } /** @@ -298,13 +303,18 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationAfterSettingRout $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(3, $pipeline); + $this->assertCount(4, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertInstanceOf(Closure::class, $route->handler); @@ -649,7 +659,7 @@ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(3, $pipeline); + $this->assertCount(4, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -665,6 +675,11 @@ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); } public function testCanSpecifyRouteNamesViaConfiguration() @@ -862,6 +877,13 @@ public function testWillCreatePipelineBasedOnMiddlewareConfiguration() 'Route middleware was registered when it should not have been' ); + $this->assertAttributeSame( + false, + 'dispatchMiddlewareIsRegistered', + $app, + 'Dispatch middleware was registered when it should not have been' + ); + $r = new ReflectionProperty($app, 'pipeline'); $r->setAccessible(true); $pipeline = $r->getValue($app); @@ -968,30 +990,6 @@ public function testRaisesDeprecationNoticeForUsageOfPreOrPostRoutingPipelineCon $this->assertTrue($triggered, 'Deprecation notice was not triggered!'); } - public function testRaisesExceptionIfRoutesAreDefinedPipelineIsPopulatedAndPipelineDoesNotProvideRoutingMiddleware() - { - // @codingStandardsIgnoreStart - $middleware = function ($request, $response, $next) {}; - // @codingStandardsIgnoreEnd - - $config = [ - 'middleware_pipeline' => [ - clone $middleware, - ], - 'routes' => [ - [ - 'path' => '/', - 'middleware' => clone $middleware, - 'allowed_methods' => [ 'GET' ], - ], - ], - ]; - $this->injectServiceInContainer($this->container, 'config', $config); - - $this->setExpectedException(InvalidArgumentException::class, 'routing middleware'); - $this->factory->__invoke($this->container->reveal()); - } - public function configWithRoutesButNoPipeline() { // @codingStandardsIgnoreStart @@ -1016,20 +1014,26 @@ public function configWithRoutesButNoPipeline() /** * @dataProvider configWithRoutesButNoPipeline */ - public function testProvidingRoutesAndNoPipelineImplicitlyRegistersRoutingMiddleware($config) + public function testProvidingRoutesAndNoPipelineImplicitlyRegistersRoutingAndDispatchMiddleware($config) { $this->injectServiceInContainer($this->container, 'config', $config); $app = $this->factory->__invoke($this->container->reveal()); $this->assertAttributeSame(true, 'routeMiddlewareIsRegistered', $app); + $this->assertAttributeSame(true, 'dispatchMiddlewareIsRegistered', $app); $r = new ReflectionProperty($app, 'pipeline'); $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(1, $pipeline, 'Did not get expected pipeline count!'); + $this->assertCount(2, $pipeline, 'Did not get expected pipeline count!'); + $test = $pipeline->dequeue(); $this->assertEquals('/', $test->path); $this->assertSame([$app, 'routeMiddleware'], $test->handler); + + $test = $pipeline->dequeue(); + $this->assertEquals('/', $test->path); + $this->assertSame([$app, 'dispatchMiddleware'], $test->handler); } public function testPipelineContainingRoutingMiddlewareConstantPipesRoutingMiddleware() @@ -1044,4 +1048,17 @@ public function testPipelineContainingRoutingMiddlewareConstantPipesRoutingMiddl $app = $this->factory->__invoke($this->container->reveal()); $this->assertAttributeSame(true, 'routeMiddlewareIsRegistered', $app); } + + public function testPipelineContainingDispatchMiddlewareConstantPipesDispatchMiddleware() + { + $config = [ + 'middleware_pipeline' => [ + ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + $this->assertAttributeSame(true, 'dispatchMiddlewareIsRegistered', $app); + } } From b58176c067ac840217bb08fbea02e78a987510a5 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 15:11:31 -0600 Subject: [PATCH 06/18] Updates route middleware tests Because routing/dispatching has been split across two separate middleware methods at this time, test setup had to change for the integration tests to ensure that the combined route/dispatch cycle is triggered. --- test/RouteMiddlewareTest.php | 102 +++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/test/RouteMiddlewareTest.php b/test/RouteMiddlewareTest.php index faae53bd..6cf3cfb6 100644 --- a/test/RouteMiddlewareTest.php +++ b/test/RouteMiddlewareTest.php @@ -89,7 +89,7 @@ public function testGeneralRoutingFailureCallsNextWithSameRequestAndResponse() $this->assertTrue($called); } - public function testRoutingSuccessResolvingToCallableMiddlewareInvokesIt() + public function testRoutingSuccessResolvingToCallableMiddlewareCanBeDispatched() { $request = new ServerRequest(); $response = new Response(); @@ -99,37 +99,39 @@ public function testRoutingSuccessResolvingToCallableMiddlewareInvokesIt() return $finalResponse; }; - $result = RouteResult::fromRouteMatch( + $result = RouteResult::fromRouteMatch( '/foo', $middleware, [] ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); $next = function ($request, $response) { $this->fail('Should not enter $next'); }; - $app = $this->getApplication(); - $test = $app->routeMiddleware($request, $response, $next); + $app = $this->getApplication(); + $test = $app->dispatchMiddleware($request, $response, $next); $this->assertSame($finalResponse, $test); } - public function testRoutingSuccessWithoutMiddlewareRaisesException() + public function testRoutingSuccessWithoutMiddlewareRaisesExceptionInDispatch() { $request = new ServerRequest(); $response = new Response(); $middleware = (object) []; - $result = RouteResult::fromRouteMatch( + $result = RouteResult::fromRouteMatch( '/foo', false, [] ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); $next = function ($request, $response) { $this->fail('Should not enter $next'); @@ -137,23 +139,24 @@ public function testRoutingSuccessWithoutMiddlewareRaisesException() $app = $this->getApplication(); $this->setExpectedException(InvalidMiddlewareException::class, 'does not have'); - $app->routeMiddleware($request, $response, $next); + $app->dispatchMiddleware($request, $response, $next); } - public function testRoutingSuccessResolvingToNonCallableNonStringMiddlewareRaisesException() + public function testRoutingSuccessResolvingToNonCallableNonStringMiddlewareRaisesExceptionAtDispatch() { $request = new ServerRequest(); $response = new Response(); $middleware = (object) []; - $result = RouteResult::fromRouteMatch( + $result = RouteResult::fromRouteMatch( '/foo', $middleware, [] ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); $next = function ($request, $response) { $this->fail('Should not enter $next'); @@ -161,23 +164,24 @@ public function testRoutingSuccessResolvingToNonCallableNonStringMiddlewareRaise $app = $this->getApplication(); $this->setExpectedException(InvalidMiddlewareException::class, 'callable'); - $app->routeMiddleware($request, $response, $next); + $app->dispatchMiddleware($request, $response, $next); } - public function testRoutingSuccessResolvingToUninvokableMiddlewareRaisesException() + public function testRoutingSuccessResolvingToUninvokableMiddlewareRaisesExceptionAtDispatch() { $request = new ServerRequest(); $response = new Response(); $middleware = (object) []; - $result = RouteResult::fromRouteMatch( + $result = RouteResult::fromRouteMatch( '/foo', 'not a class', [] ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); // No container for this one, to ensure we marshal only a potential object instance. $app = new Application($this->router->reveal()); @@ -187,10 +191,10 @@ public function testRoutingSuccessResolvingToUninvokableMiddlewareRaisesExceptio }; $this->setExpectedException(InvalidMiddlewareException::class, 'callable'); - $app->routeMiddleware($request, $response, $next); + $app->dispatchMiddleware($request, $response, $next); } - public function testRoutingSuccessResolvingToInvokableMiddlewareCallsIt() + public function testRoutingSuccessResolvingToInvokableMiddlewareCallsItAtDispatch() { $request = new ServerRequest(); $response = new Response(); @@ -201,6 +205,7 @@ public function testRoutingSuccessResolvingToInvokableMiddlewareCallsIt() ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); // No container for this one, to ensure we marshal only a potential object instance. $app = new Application($this->router->reveal()); @@ -209,13 +214,13 @@ public function testRoutingSuccessResolvingToInvokableMiddlewareCallsIt() $this->fail('Should not enter $next'); }; - $test = $app->routeMiddleware($request, $response, $next); + $test = $app->dispatchMiddleware($request, $response, $next); $this->assertInstanceOf(ResponseInterface::class, $test); $this->assertTrue($test->hasHeader('X-Invoked')); $this->assertEquals(__NAMESPACE__ . '\TestAsset\InvokableMiddleware', $test->getHeaderLine('X-Invoked')); } - public function testRoutingSuccessResolvingToContainerMiddlewareCallsIt() + public function testRoutingSuccessResolvingToContainerMiddlewareCallsItAtDispatch() { $request = new ServerRequest(); $response = new Response(); @@ -223,13 +228,14 @@ public function testRoutingSuccessResolvingToContainerMiddlewareCallsIt() return $res->withHeader('X-Middleware', 'Invoked'); }; - $result = RouteResult::fromRouteMatch( + $result = RouteResult::fromRouteMatch( '/foo', 'TestAsset\Middleware', [] ); $this->router->match($request)->willReturn($result); + $request = $request->withAttribute(RouteResult::class, $result); $this->injectServiceInContainer($this->container, 'TestAsset\Middleware', $middleware); @@ -239,7 +245,7 @@ public function testRoutingSuccessResolvingToContainerMiddlewareCallsIt() $this->fail('Should not enter $next'); }; - $test = $app->routeMiddleware($request, $response, $next); + $test = $app->dispatchMiddleware($request, $response, $next); $this->assertInstanceOf(ResponseInterface::class, $test); $this->assertTrue($test->hasHeader('X-Middleware')); $this->assertEquals('Invoked', $test->getHeaderLine('X-Middleware')); @@ -338,13 +344,17 @@ public function testRoutingWithSamePathWithoutName($adapter) $request = new ServerRequest([ 'REQUEST_METHOD' => 'GET' ], [], '/foo', 'GET'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'POST' ], [], '/foo', 'POST'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware POST', (string) $result->getBody()); } @@ -363,13 +373,17 @@ public function testRoutingWithSamePathWithName($adapter) $request = new ServerRequest([ 'REQUEST_METHOD' => 'GET' ], [], '/foo', 'GET'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'POST' ], [], '/foo', 'POST'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware POST', (string) $result->getBody()); } @@ -388,13 +402,17 @@ public function testRoutingWithSamePathWithRouteWithoutName($adapter) $request = new ServerRequest([ 'REQUEST_METHOD' => 'GET' ], [], '/foo', 'GET'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'POST' ], [], '/foo', 'POST'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware POST', (string) $result->getBody()); } @@ -412,13 +430,17 @@ public function testRoutingWithSamePathWithRouteWithName($adapter) $request = new ServerRequest([ 'REQUEST_METHOD' => 'GET' ], [], '/foo', 'GET'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'POST' ], [], '/foo', 'POST'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware POST', (string) $result->getBody()); } @@ -445,17 +467,23 @@ public function testRoutingWithSamePathWithRouteWithMultipleMethods($adapter) $request = new ServerRequest([ 'REQUEST_METHOD' => 'GET' ], [], '/foo', 'GET'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET, POST', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'POST' ], [], '/foo', 'POST'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware GET, POST', (string) $result->getBody()); $request = new ServerRequest([ 'REQUEST_METHOD' => 'DELETE' ], [], '/foo', 'DELETE'); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware DELETE', (string) $result->getBody()); } @@ -488,7 +516,9 @@ public function testMatchWithAllHttpMethods($adapter, $method) $request = new ServerRequest([ 'REQUEST_METHOD' => $method ], [], '/foo', $method); $response = new Response(); - $result = $app->routeMiddleware($request, $response, $next); + $result = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertEquals('Middleware', (string) $result->getBody()); } @@ -542,7 +572,9 @@ public function testInjectsRouteResultAsAttribute() $this->router->match($request)->willReturn($result); $app = $this->getApplication(); - $test = $app->routeMiddleware($request, $response, $next); + $test = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertSame($response, $test); $this->assertTrue($triggered); } @@ -570,7 +602,9 @@ public function testMiddlewareTriggersObserversWithSuccessfulRouteResult() $app->attachRouteResultObserver($routeResultObserver->reveal()); - $test = $app->routeMiddleware($request, $response, $next); + $test = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertSame($response, $test); } @@ -635,7 +669,9 @@ public function testDetachedRouteResultObserverIsNotTriggered() $app->detachRouteResultObserver($routeResultObserver->reveal()); $this->assertAttributeNotContains($routeResultObserver->reveal(), 'routeResultObservers', $app); - $test = $app->routeMiddleware($request, $response, $next); + $test = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + }); $this->assertSame($response, $test); } From 3cbfd19ece360aa76a41b87c2a60375bc783ab0c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 16:25:19 -0600 Subject: [PATCH 07/18] Deprecate route result observers This patch deprecates route result observers for the upcoming RC6. Route result observers may still be used, but: - new `Application::routeResultObserverMiddleware()` must be piped to the application, immediately following the `routeMiddleware()`; - observers are no longer notified in the case of routing failure; - if you have switched to the new `ApplicationFactory` configuration, you will need to add the route result observer middleware to the middleware pipeline. Observers can be easily rewritten as middleware, and the migration guide demonstrates this. --- ...ting-locale-depending-routing-parameter.md | 216 ++---------------- doc/book/helpers/server-url-helper.md | 14 +- doc/book/helpers/url-helper.md | 24 +- doc/book/migration/rc-to-v1.md | 135 ++++++++++- doc/book/router/result-observers.md | 7 + src/Application.php | 57 ++++- src/Container/ApplicationFactory.php | 14 ++ test/Container/ApplicationFactoryTest.php | 59 ++++- test/RouteMiddlewareTest.php | 39 ++-- 9 files changed, 315 insertions(+), 250 deletions(-) diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index 6aaffe0e..a624132e 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -99,13 +99,11 @@ return [ > simply cut-and-paste them without modification. -## Create a route result observer class for localization +## Create a route result middleware class for localization To make sure that you can setup the locale after the routing has been processed, -you need to implement a localization observer which implements the -`RouteResultObserverInterface`. All classes that implement this interface and -that are attached to the `Zend\Expressive\Application` instance get called -whenever the `RouteResult` has changed. +you need to implement localization middleware that acts on the route result, and +registered in the pipeline immediately following the routing middleware. Such a `LocalizationObserver` class could look similar to this: @@ -114,230 +112,44 @@ namespace Application\I18n; use Locale; use Zend\Expressive\Router\RouteResult; -use Zend\Expressive\Router\RouteResultObserverInterface; -class LocalizationObserver implements RouteResultObserverInterface +class LocalizationObserver { - public function update(RouteResult $result) + public function __invoke($request, $response, $next) { - if ($result->isFailure()) { - return; + $result = $request->getAttribute(RouteResult::class, false); + if (! $result) { + return $next($request, $response); } $matchedParams = $result->getMatchedParams(); $lang = isset($matchedParams['lang']) ? $matchedParams['lang'] : 'de_DE'; Locale::setDefault($matchedParams['lang']); - } -} -``` - -Afterwards you need to configure the `LocalizationObserver` in your -`/config/autoload/dependencies.global.php` file: - -```php -return [ - 'dependencies' => [ - 'invokables' => [ - /* ... */ - - Application\I18n\LocalizationObserver::class => - Application\I18n\LocalizationObserver::class, - ], - - /* ... */ - ] -]; -``` - -## Attach the localization observer to the application - -There are five approaches you can take to attach the `LocalizationObserver` to -the application instance, each with pros and cons: - -### Bootstrap script - -Modify the bootstrap script `/public/index.php` to attach the observer: - -```php -use Application\I18n\LocalizationObserver; - -/* ... */ - -$app = $container->get('Zend\Expressive\Application'); -$app->attachRouteResultObserver( - $container->get(LocalizationObserver::class) -); -$app->run(); -``` - -This is likely the simplest way, but means that there may be a growing -amount of code in that file. - -### Observer factory - -Alternately, in the factory for your observer, have it self-attach to the -application instance: - -```php -// get instance of observer... - -// and now check for the Application: -if ($container->has(Application::class)) { - $container->get(Application::class)->attachRouteResultObserver($observer); -} - -return $observer; -``` - -There are two things to be careful of with this approach: - -- Circular dependencies. If a a dependency of the Application is dependent on - your observer, you'll run into this. -- Late registration. If this is injected as a dependency for another class after - routing has happened, then your observer will never be triggered. - -If you can prevent circular dependencies, and ensure that the factory is invoked -early enough, then this is a great, portable way to accomplish it. - -### Delegator factory - -If you're using zend-servicemanager, you can use a delegator factory on the -Application service to pull and register the observer: - -```php -use Zend\Expressive\Application; -use Zend\ServiceManager\DelegatorFactoryInterface; -use Zend\ServiceManager\ServiceLocatorInterface; - -class ApplicationObserverDelegatorFactory implements DelegatorFactoryInterface -{ - public function createDelegatorForName( - ServiceLocatorInterface $container, - $name, - $requestedName, - $callback - ) { - $application = $callback(); - - if (! $container->has(LocalizationObserver::class)) { - return $application; - } - - $application->attachRouteResultObserver( - $container->get(LocalizationObserver::class) - ); - return $application; - } -} -``` -Then register it as a delegator factory in `config/autoload/dependencies.global.php`: - -```php -return [ - 'dependencies' => [ - 'delegator_factories' => [ - Zend\Expressive\Application::class => [ - ApplicationObserverDelegatorFactory::class, - ], - ], - /* ... */ - ], -]; -``` - -This approach removes the probability of a circular dependency, and ensures -that the observer is attached as early as possible. - -The problem with this approach, though, is portability. You can do something -similar to this with Pimple: - -```php -$pimple->extend(Application::class, function ($app, $container) { - $app->attachRouteResultObserver($container->get(LocalizationObserver::class)); - return $app; -}); -``` - -and there are ways to accomplish it in Aura.Di as well — but they're all -different, making the approach non-portable. - -### Extend the Application factory - -Alternately, extend the Application factory: - -```php -class MyApplicationFactory extends ApplicationFactory -{ - public function __invoke($container) - { - $app = parent::__invoke($container); - $app->attachRouteResultObserver($container->get(LocalizationObserver::class)); - return $app; - } -} -``` - -Then alter the line in `config/autoload/dependencies.global.php` that registers -the `Application` factory to point at your own factory. - -This approach will work across all container types, and is essentially a -portable way of doing delegator factories. - -### Use middleware - -Alternately, use the middleware pipeline to accomplish the task. Register the -middleware early in the pipeline (before the routing middleware); the middleware -will get both the observer and application as dependencies, and simply register -the observer with the application: - -```php -use Zend\Expressive\Router\RouteResultSubjectInterface; - -class LocalizationObserverMiddleware -{ - private $application; - private $observer; - - public function __construct(LocalizationObserver $observer, RouteResultSubjectInterface $application) - { - $this->observer = $observer; - $this->application = $application; - } - - public function __invoke($request, $response, callable $next) - { - $this->application->attachRouteResultObserver($this->observer); return $next($request, $response); } } ``` -The factory would inject the observer and application instances; we leave this -as an exercise to the reader. - -In your `config/autoload/middleware-pipeline.global.php`, you'd do the following: +In your `config/autoload/middleware-pipeline.global.php`, you'd register the +dependency, and inject the middleware into the pipeline following the routing +middleware: ```php return [ 'dependencies' => [ - 'factories' => [ - LocalizationObserverMiddleware::class => LocalizationObserverMiddlewareFactory::class, + 'invokables' => [ + LocalizationObserver::class => LocalizationObserver::class, /* ... */ ], /* ... */ ], 'middleware_pipeline' => [ - [ 'middleware' => LocalizationObserverMiddleware::class ], /* ... */ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + [ 'middleware' => LocalizationObserver::class ], /* ... */ ], ]; ``` - -This approach is also portable, but, as you can see, requires more setup (a -middleware class + factory + factory registration + middleware registration). -On the flip side, it's portable between applications, which could be something -to consider if you were to make the functionality into a discrete package. diff --git a/doc/book/helpers/server-url-helper.md b/doc/book/helpers/server-url-helper.md index 53a2f9ce..a6e22a83 100644 --- a/doc/book/helpers/server-url-helper.md +++ b/doc/book/helpers/server-url-helper.md @@ -47,8 +47,8 @@ As such, you will need to: - Register the `ServerUrlHelper` as a service in your container. - Register the `ServerUrlMiddleware` as a service in your container. -- Register the `ServerUrlMiddleware` as pipeline middleware, early in the - pipeline. +- Register the `ServerUrlMiddleware` as pipeline middleware, immediately + following the routing middleware. The following examples demonstrate registering the services. @@ -79,20 +79,22 @@ $container->set( ); ``` -To register the `ServerUrlMiddleware` as pre-routing pipeline middleware: +To register the `ServerUrlMiddleware` as pipeline middleware following the +routing middleware: ```php use Zend\Expressive\Helper\ServerUrlMiddleware; -// Do this early, before piping other middleware or routes: +// Programmatically: +$app->pipeRoutingMiddleware(); $app->pipe(ServerUrlMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ -// ['middleware' => ServerUrlMiddleware::class], // /* ... */ // Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// ['middleware' => ServerUrlMiddleware::class], // /* ... */ // ], // ] @@ -112,8 +114,8 @@ return [ ], ], 'middleware_pipeline' => [ - ['middleware' => ServerUrlMiddleware::class], Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + ['middleware' => ServerUrlMiddleware::class], ], ]; ``` diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 6159077f..7450e7de 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -2,9 +2,9 @@ `Zend\Expressive\Helper\UrlHelper` provides the ability to generate a URI path based on a given route defined in the `Zend\Expressive\Router\RouterInterface`. -If registered as a route result observer, and the route being used was also -the one matched during routing, you can provide a subset of routing -parameters, and any not provided will be pulled from those matched. +If injected with a route result, and the route being used was also the one +matched during routing, you can provide a subset of routing parameters, and any +not provided will be pulled from those matched. ## Usage @@ -58,16 +58,16 @@ In order to use the helper, you will need to instantiate it with the current `RouterInterface`. The factory `Zend\Expressive\Helper\UrlHelperFactory` has been provided for this purpose, and can be used trivially with most dependency injection containers implementing container-interop. Additionally, -it is most useful when injected with the current results of routing, and as -such should be registered as a route result observer with the application. The -following steps should be followed to register and configure the helper: +it is most useful when injected with the current results of routing, which +requires registering middleware with the application that can inject the route +result. The following steps should be followed to register and configure the helper: - Register the `UrlHelper` as a service in your container, using the provided factory. - Register the `UrlHelperMiddleware` as a service in your container, using the provided factory. -- Register the `UrlHelperMiddleware` as pipeline middleware, early in the - pipeline. +- Register the `UrlHelperMiddleware` as pipeline middleware, immediately + following the routing middleware. ### Registering the helper service @@ -113,20 +113,22 @@ return ['dependencies' => [ ### Registering the pipeline middleware -To register the `UrlHelperMiddleware` as pre-routing pipeline middleware: +To register the `UrlHelperMiddleware` as pipeline middleware following the +routing middleware: ```php use Zend\Expressive\Helper\UrlHelperMiddleware; -// Do this early, before piping other middleware or routes: +// Programmatically: +$app->pipeRoutingMiddleware(); $app->pipe(UrlHelperMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ -// ['middleware' => UrlHelperMiddleware::class], // /* ... */ // Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// ['middleware' => UrlHelperMiddleware::class], // /* ... */ // ], // ] diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index 16121595..be6d8123 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -8,6 +8,8 @@ RC6 introduced changes to the following: middleware. - The above change also suggested an alternative to the middleware pipeline configuration that simplifies it. +- Route result observers are deprecated, and no longer triggered for routing + failures. ## Routing and Dispatch middleware @@ -222,7 +224,136 @@ To update an existing application: Once you have made the above changes, you should no longer receive deprecation notices when running your application. +## Route result observer deprecation + +As of RC6, the following changes have occurred with regards to route result +observers: + +- They are deprecated for usage with `Zend\Expressive\Application`, and that + class will not be a route result subject starting in 1.1. You will need to + start migrating to alternative solutions. +- The functionality for notifying observers has been moved from the routing + middleware into a dedicated `Application::routeResultObserverMiddleware()` + method. This middleware must be piped separately to the middleware pipeline + for it to trigger. + +### Impact + +If you are using any route result observers, you will need to ensure your +application notifies them, and you will want to migrate to alternative solutions +to ensure your functionality continues to work. + +To ensure your observers are triggered, you will need to adapt your application, +based on how you create your instance. + +If you are *not* using the `ApplicationFactory`, you will need to pipe the +`routeResultObserverMiddleware` to your application, between the routing and +dispatch middleware: + +```php +$app->pipeRoutingMiddleware(); +$app->pipeRouteResultObserverMiddleware(); +$app->pipeDispatchMiddleware(); +``` + +If you are using the `ApplicationFactory`, you may need to update your +configuration to allow injecting the route result observer middleware. If you +have *not* updated your configuration to remove the `pre_routing` and/or +`post_routing` keys, the middleware *will* be registered for you. If you have, +however, you will need to register it following the routing middleware: + +```php +[ + 'middleware_pipeline' => [ + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + /* ... */ + ], +] +``` + +To make your observers forwards-compatible requires two changes: + +- Rewriting your observer as middleware. +- Registering your observer as middleware following the routing middleware. + +If your observer looked like the following: + +```php +use Zend\Expressive\Router\RouteResult; +use Zend\Expressive\Router\RouteResultObserverInterface; + +class MyObserver implements RouteResultObserverInterface +{ + private $logger; + + public function __construct(Logger $logger) + { + $this->logger = $logger; + } + + public function update(RouteResult $result) + { + $this->logger->log($result); + } +} +``` + +You could rewrite it as follows: + +```php +use Zend\Expressive\Router\RouteResult; + +class MyObserver +{ + private $logger; + + public function __construct(Logger $logger) + { + $this->logger = $logger; + } + + public function __invoke($request, $response, $next) + { + $result = $request->getAttribute(RouteResult::class, false); + if (! $result) { + return $next($request, $response); + } + + $this->logger->log($result); + return $next($request, $response); + } +} +``` + +You would then register it following the routing middleware. If you are building +your application programmatically, you would do this as follows: + +```php +$app->pipeRoutingMiddleware(); +$app->pipe(MyObserver::class); +$app->pipeDispatchMiddleware(); +``` + +If you are using the `ApplicationFactory`, alter your configuration: + +```php +[ + 'middleware_pipeline' => [ + /* ... */ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + ['middleware' => MyObserver::class], + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + /* ... */ + ], +] +``` + ## Timeline for migration -Support for the `pre_routing` and `post_routing` configuration will be removed -with the 1.1.0 release. +The following features will be removed in version 1.1.0: + +- Support for the `pre_routing` and `post_routing` configuration. +- Support for route result observers. diff --git a/doc/book/router/result-observers.md b/doc/book/router/result-observers.md index 8dd2e1b0..26760564 100644 --- a/doc/book/router/result-observers.md +++ b/doc/book/router/result-observers.md @@ -1,5 +1,12 @@ # Route Result Observers +> ## DEPRECATED! +> +> The route result observers feature existed prior to the stable 1.0 release, +> but was deprecated with 1.0.0RC6. Please do not use this feature; instead, +> you can inject middleware between the routing and dispatch middleware that can +> act on the matched route result. + Occasionally, you may have need of the `RouteResult` within other application code. As a primary example, a URI generator may want this information to allow creating "self" URIs, or to allow presenting a subset of parameters to generate diff --git a/src/Application.php b/src/Application.php index 7a026a22..8c67c450 100644 --- a/src/Application.php +++ b/src/Application.php @@ -23,6 +23,8 @@ /** * Middleware application providing routing based on paths and HTTP methods. * + * @todo For 1.1, remove the RouteResultSubjectInterface implementation, and + * all deprecated properties and methods. * @method Router\Route get($path, $middleware, $name = null) * @method Router\Route post($path, $middleware, $name = null) * @method Router\Route put($path, $middleware, $name = null) @@ -76,6 +78,13 @@ class Application extends MiddlewarePipe implements Router\RouteResultSubjectInt */ private $router; + /** + * @deprecated This property will be removed in v1.1. + * @var bool Flag indicating whether or not the route result observer + * middleware is registered in the middleware pipeline. + */ + private $routeResultObserverMiddlewareIsRegistered = false; + /** * Observers to trigger once we have a route result. * @@ -183,6 +192,7 @@ public function any($path, $middleware, $name = null) /** * Attach a route result observer. * + * @deprecated This method will be removed in v1.1. * @param Router\RouteResultObserverInterface $observer */ public function attachRouteResultObserver(Router\RouteResultObserverInterface $observer) @@ -193,6 +203,7 @@ public function attachRouteResultObserver(Router\RouteResultObserverInterface $o /** * Detach a route result observer. * + * @deprecated This method will be removed in v1.1. * @param Router\RouteResultObserverInterface $observer */ public function detachRouteResultObserver(Router\RouteResultObserverInterface $observer) @@ -206,6 +217,7 @@ public function detachRouteResultObserver(Router\RouteResultObserverInterface $o /** * Notify all route result observers with the given route result. * + * @deprecated This method will be removed in v1.1. * @param Router\RouteResult */ public function notifyRouteResultObservers(Router\RouteResult $result) @@ -354,6 +366,20 @@ public function pipeDispatchMiddleware() $this->pipe([$this, 'dispatchMiddleware']); } + /** + * Register the route result observer middleware in the middleware pipeline. + * + * @deprecated This method will be removed in v1.1. + */ + public function pipeRouteResultObserverMiddleware() + { + if ($this->routeResultObserverMiddlewareIsRegistered) { + return; + } + $this->pipe([$this, 'routeResultObserverMiddleware']); + $this->routeResultObserverMiddlewareIsRegistered = true; + } + /** * Middleware that routes the incoming request and delegates to the matched middleware. * @@ -379,7 +405,6 @@ public function pipeDispatchMiddleware() public function routeMiddleware(ServerRequestInterface $request, ResponseInterface $response, callable $next) { $result = $this->router->match($request); - $this->notifyRouteResultObservers($result); if ($result->isFailure()) { if ($result->isMethodFailure()) { @@ -437,6 +462,36 @@ public function dispatchMiddleware(ServerRequestInterface $request, ResponseInte return $middleware($request, $response, $next); } + /** + * Middleware for notifying route result observers. + * + * If the request has a route result, calls notifyRouteResultObservers(). + * + * This middleware should be injected between the routing and dispatch + * middleware when creating your middleware pipeline. + * + * If you are using this, rewrite your observers as middleware that + * pulls the route result from the request instead. + * + * @deprecated This method will be removed in v1.1. + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callable $next + * @returns ResponseInterface + */ + public function routeResultObserverMiddleware( + ServerRequestInterface $request, + ResponseInterface $response, + callable $next + ) { + $result = $request->getAttribute(Router\RouteResult::class, false); + if ($result) { + $this->notifyRouteResultObservers($result); + } + + return $next($request, $response); + } + /** * Add a route for the route middleware to match. * diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index aefa1c86..91bc4047 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -112,6 +112,11 @@ class ApplicationFactory const DISPATCH_MIDDLEWARE = 'EXPRESSIVE_DISPATCH_MIDDLEWARE'; const ROUTING_MIDDLEWARE = 'EXPRESSIVE_ROUTING_MIDDLEWARE'; + /** + * @deprecated This constant will be removed in v1.1. + */ + const ROUTE_RESULT_OBSERVER_MIDDLEWARE = 'EXPRESSIVE_ROUTE_RESULT_OBSERVER_MIDDLEWARE'; + /** * Create and return an Application instance. * @@ -252,6 +257,7 @@ private function handleDeprecatedPipeline(array $deprecatedKeys, array $pipeline } $app->pipeRoutingMiddleware(); + $app->pipeRouteResultObserverMiddleware(); $app->pipeDispatchMiddleware(); if (isset($pipeline['post_routing'])) { @@ -311,6 +317,7 @@ private function injectRoutes(array $routes, Application $app) /** * Given a collection of middleware specifications, pipe them to the application. * + * @todo Remove ROUTE_RESULT_OBSERVER_MIDDLEWARE detection for 1.1 * @param array $collection * @param Application $app * @return bool Flag indicating whether or not any middleware was injected. @@ -328,6 +335,13 @@ private function injectMiddleware(array $collection, Application $app) $spec = ['middleware' => [$app, 'dispatchMiddleware']]; } + if ($spec === self::ROUTE_RESULT_OBSERVER_MIDDLEWARE) { + $spec = ['middleware' => [$app, 'routeResultObserverMiddleware']]; + $r = new \ReflectionProperty($app, 'routeResultObserverMiddlewareIsRegistered'); + $r->setAccessible(true); + $r->setValue($app, true); + } + if (! is_array($spec) || ! array_key_exists('middleware', $spec)) { continue; } diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php index 1e229840..ee09d0b7 100644 --- a/test/Container/ApplicationFactoryTest.php +++ b/test/Container/ApplicationFactoryTest.php @@ -238,7 +238,7 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(4, $pipeline); + $this->assertCount(5, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -255,6 +255,11 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationPriorToSettingRo $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'routeResultObserverMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); @@ -303,13 +308,18 @@ public function testCanPipeMiddlewareProvidedDuringConfigurationAfterSettingRout $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(4, $pipeline); + $this->assertCount(5, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'routeResultObserverMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); @@ -659,7 +669,7 @@ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() $r->setAccessible(true); $pipeline = $r->getValue($app); - $this->assertCount(4, $pipeline); + $this->assertCount(5, $pipeline); $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); @@ -676,6 +686,11 @@ public function testWillPipeRoutingMiddlewareEvenIfNoRoutesAreRegistered() $this->assertSame([$app, 'routeMiddleware'], $route->handler); $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); + $this->assertInstanceOf(StratigilityRoute::class, $route); + $this->assertSame([$app, 'routeResultObserverMiddleware'], $route->handler); + $this->assertEquals('/', $route->path); + $route = $pipeline->dequeue(); $this->assertInstanceOf(StratigilityRoute::class, $route); $this->assertSame([$app, 'dispatchMiddleware'], $route->handler); @@ -1061,4 +1076,42 @@ public function testPipelineContainingDispatchMiddlewareConstantPipesDispatchMid $app = $this->factory->__invoke($this->container->reveal()); $this->assertAttributeSame(true, 'dispatchMiddlewareIsRegistered', $app); } + + /** + * @deprecated This test can be removed for 1.1 + */ + public function testPipelineContainingRouteResultObserverMiddlewareConstantPipesRelatedMiddleware() + { + $config = [ + 'middleware_pipeline' => [ + ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE, + ], + ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + $this->assertAttributeSame(true, 'routeResultObserverMiddlewareIsRegistered', $app); + } + + /** + * @dataProvider middlewarePipelinesWithPreOrPostRouting + * @deprecated This test can be removed for 1.1 + */ + public function testUsageOfDeprecatedConfigurationRegistersRouteResultObserverMiddleware($config) + { + $config = ['middleware_pipeline' => $config]; + $this->injectServiceInContainer($this->container, 'config', $config); + + // @codingStandardsIgnoreStart + set_error_handler(function ($errno, $errmsg) use (&$triggered) { + $this->assertContains('routing', $errmsg); + $triggered = true; + }, E_USER_DEPRECATED); + // @codingStandardsIgnoreEnd + + $app = $this->factory->__invoke($this->container->reveal()); + restore_error_handler(); + + $this->assertAttributeSame(true, 'routeResultObserverMiddlewareIsRegistered', $app); + } } diff --git a/test/RouteMiddlewareTest.php b/test/RouteMiddlewareTest.php index 6cf3cfb6..aa784e0e 100644 --- a/test/RouteMiddlewareTest.php +++ b/test/RouteMiddlewareTest.php @@ -603,34 +603,17 @@ public function testMiddlewareTriggersObserversWithSuccessfulRouteResult() $app->attachRouteResultObserver($routeResultObserver->reveal()); $test = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { - return $app->dispatchMiddleware($request, $response, $next); + return $app->routeResultObserverMiddleware( + $request, + $response, + function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + } + ); }); $this->assertSame($response, $test); } - public function testMiddlewareTriggersObserversWithFailedRouteResult() - { - $request = new ServerRequest(); - $response = new Response(); - $result = RouteResult::fromRouteFailure(['GET', 'POST']); - - $routeResultObserver = $this->prophesize(RouteResultObserverInterface::class); - $routeResultObserver->update($result)->shouldBeCalled(); - $this->router->match($request)->willReturn($result); - - $next = function ($request, $response, $error = false) { - $this->assertEquals(405, $error); - $this->assertEquals(405, $response->getStatusCode()); - return $response; - }; - - $app = $this->getApplication(); - $app->attachRouteResultObserver($routeResultObserver->reveal()); - - $test = $app->routeMiddleware($request, $response, $next); - $this->assertEquals(405, $test->getStatusCode()); - } - public function testCanDetachRouteResultObservers() { $routeResultObserver = $this->prophesize(RouteResultObserverInterface::class); @@ -670,7 +653,13 @@ public function testDetachedRouteResultObserverIsNotTriggered() $this->assertAttributeNotContains($routeResultObserver->reveal(), 'routeResultObservers', $app); $test = $app->routeMiddleware($request, $response, function ($request, $response) use ($app, $next) { - return $app->dispatchMiddleware($request, $response, $next); + return $app->routeResultObserverMiddleware( + $request, + $response, + function ($request, $response) use ($app, $next) { + return $app->dispatchMiddleware($request, $response, $next); + } + ); }); $this->assertSame($response, $test); } From 913337b3335ee0cb9adad1aa7d3899d494784ed4 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jan 2016 17:15:24 -0600 Subject: [PATCH 08/18] Incorporated feedbacy from @danizord --- doc/book/application.md | 2 +- doc/book/container/factories.md | 5 +++-- doc/book/cookbook/custom-404-page-handling.md | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/book/application.md b/doc/book/application.md index 1a423d3b..f57e12e3 100644 --- a/doc/book/application.md +++ b/doc/book/application.md @@ -188,7 +188,7 @@ Read the section on [piping vs routing](router/piping.md) for more information. ### Registering routing and dispatch middleware -Routing is accomplished via dedicated a dedicated middleware method, +Routing is accomplished via a dedicated middleware method, `Application::routeMiddleware()`; similarly, dispatching of routed middleware has a corresponding instance middleware method, `Application::dispatchMiddleware()`. Each can be piped/registered with other middleware platforms if desired. diff --git a/doc/book/container/factories.md b/doc/book/container/factories.md index a4acf6dc..dd7f33ea 100644 --- a/doc/book/container/factories.md +++ b/doc/book/container/factories.md @@ -42,12 +42,13 @@ order to seed the `Application` instance: // An array of middleware to register. [ /* ... */ ], Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, [ /* ... */ ], ], ``` - Each item of the array, other than the entry for routing middleware, must be - an array itself, with the following structure: + Each item of the array, other than the entries for routing and dispatch + middleware, must be an array itself, with the following structure: ```php [ diff --git a/doc/book/cookbook/custom-404-page-handling.md b/doc/book/cookbook/custom-404-page-handling.md index d5561f74..c6dc1263 100644 --- a/doc/book/cookbook/custom-404-page-handling.md +++ b/doc/book/cookbook/custom-404-page-handling.md @@ -87,12 +87,12 @@ routed, and thus needs to be piped to the application instance. You can do this via either configuration, or manually. To do this via configuration, add an entry under the `middleware_pipeline` -configuration, after the routing middleware: +configuration, after the dispatch middleware: ```php 'middleware_pipeline' => [ /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, [ 'middleware' => 'Application\NotFound', ], From 8a9e4ae2d517bfc5d80996e1801f54286ab4152e Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sat, 16 Jan 2016 15:36:39 -0600 Subject: [PATCH 09/18] Implemented priority for middleware specifications This patch implements priority for middleware specifications provided to configuration used by the `ApplicationFactory`. Each specification can now include a `priority` key with an integer value; if none is provided, `1` is assumed as the default. The `ApplicationFactory` uses the priority to seed an `SplPriorityQueue` instance, which is then iterated when piping to the application. The priority queue is seeded using similar semantics to those in zend-stdlib, ensuring that middleware with the same priority are dequeued in the same order of attachment. The changes allow usage of the constant values indicating routing/dispatch/route result observer middleware both as middleware pipeline values, as well as inside `middleware` array specifications. These special values *always* default to priority 1. The migration path for developers is to add `priority` values to their middleware specs; until that is done, we cannot guarantee order of merged values! --- doc/book/container/factories.md | 8 +- doc/book/cookbook/custom-404-page-handling.md | 1 + doc/book/cookbook/debug-toolbars.md | 1 + doc/book/cookbook/route-specific-pipeline.md | 1 + ...ting-locale-depending-routing-parameter.md | 10 +- ...etting-locale-without-routing-parameter.md | 1 + doc/book/cookbook/using-a-base-path.md | 2 +- .../cookbook/using-zend-form-view-helpers.md | 2 +- doc/book/helpers/body-parse.md | 2 +- doc/book/helpers/server-url-helper.md | 4 +- doc/book/helpers/url-helper.md | 18 ++- doc/book/migration/rc-to-v1.md | 64 ++++++-- doc/book/usage-examples.md | 56 ++++++- src/Container/ApplicationFactory.php | 145 ++++++++++++++--- test/Container/ApplicationFactoryTest.php | 151 +++++++++++++++++- 15 files changed, 411 insertions(+), 55 deletions(-) diff --git a/doc/book/container/factories.md b/doc/book/container/factories.md index dd7f33ea..6eae1997 100644 --- a/doc/book/container/factories.md +++ b/doc/book/container/factories.md @@ -57,6 +57,7 @@ order to seed the `Application` instance: // optional: 'path' => '/path/to/match', 'error' => true, + 'priority' => 1, // Integer ], ``` @@ -67,7 +68,12 @@ order to seed the `Application` instance: `error` key is present and boolean `true`, then the middleware will be registered as error middleware. (This is necessary due to the fact that the factory defines a callable wrapper around middleware to enable lazy-loading of - middleware.) + middleware.) The `priority` defaults to 1, and follows the semantics of + [SplPriorityQueue](http://php.net/SplPriorityQueue): higher integer values + indicate higher priority (will execute earlier), while lower/negative integer + values indicate lower priority (will execute last). Default priority is 1; use + granular priority values to specify the order in which middleware should be + piped to the application. - `routes` is used to define routed middleware. The value must be an array, consisting of arrays defining each middleware: diff --git a/doc/book/cookbook/custom-404-page-handling.md b/doc/book/cookbook/custom-404-page-handling.md index c6dc1263..96288555 100644 --- a/doc/book/cookbook/custom-404-page-handling.md +++ b/doc/book/cookbook/custom-404-page-handling.md @@ -95,6 +95,7 @@ configuration, after the dispatch middleware: Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, [ 'middleware' => 'Application\NotFound', + 'priority' => -1, ], /* ... */ ], diff --git a/doc/book/cookbook/debug-toolbars.md b/doc/book/cookbook/debug-toolbars.md index 52e03191..31f08739 100644 --- a/doc/book/cookbook/debug-toolbars.md +++ b/doc/book/cookbook/debug-toolbars.md @@ -78,6 +78,7 @@ return [ 'middleware' => [ PhpDebugBarMiddleware::class, ], + 'priority' => 1000, ], ], ]; diff --git a/doc/book/cookbook/route-specific-pipeline.md b/doc/book/cookbook/route-specific-pipeline.md index 01e1e9b3..f13c621c 100644 --- a/doc/book/cookbook/route-specific-pipeline.md +++ b/doc/book/cookbook/route-specific-pipeline.md @@ -176,6 +176,7 @@ return [ 'BodyParsingMiddleware', 'ValidationMiddleware', ], + 'priority' => 100, ], ], ]; diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index a624132e..99762117 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -147,8 +147,14 @@ return [ ], 'middleware_pipeline' => [ /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - [ 'middleware' => LocalizationObserver::class ], + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + [ 'middleware' => LocalizationObserver::class ], + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ]; diff --git a/doc/book/cookbook/setting-locale-without-routing-parameter.md b/doc/book/cookbook/setting-locale-without-routing-parameter.md index 269ecd3d..81cd0f26 100644 --- a/doc/book/cookbook/setting-locale-without-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-without-routing-parameter.md @@ -107,6 +107,7 @@ return [ Application\I18n\SetLanguageMiddleware::class, /* ... */ ], + 'priority' => 1000, ], Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, diff --git a/doc/book/cookbook/using-a-base-path.md b/doc/book/cookbook/using-a-base-path.md index 246f1d7a..64fa73dd 100644 --- a/doc/book/cookbook/using-a-base-path.md +++ b/doc/book/cookbook/using-a-base-path.md @@ -106,7 +106,7 @@ return [ /* ... */ ], 'middleware_pipeline' => [ - [ 'middleware' => [ Blast\BaseUrl\BaseUrlMiddleware::class ] ], + [ 'middleware' => [ Blast\BaseUrl\BaseUrlMiddleware::class ], 'priority' => 1000 ], /* ... */ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, /* ... */ diff --git a/doc/book/cookbook/using-zend-form-view-helpers.md b/doc/book/cookbook/using-zend-form-view-helpers.md index e0d45c81..bbeb305c 100644 --- a/doc/book/cookbook/using-zend-form-view-helpers.md +++ b/doc/book/cookbook/using-zend-form-view-helpers.md @@ -224,7 +224,7 @@ return [ /* ... */ ], 'middleware_pipeline' => [ - ['middleware' => Your\Application\FormHelpersMiddleware::class], + ['middleware' => Your\Application\FormHelpersMiddleware::class, 'priority' => 1000], /* ... */ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, /* ... */ diff --git a/doc/book/helpers/body-parse.md b/doc/book/helpers/body-parse.md index 82cdfaf2..bddc6e34 100644 --- a/doc/book/helpers/body-parse.md +++ b/doc/book/helpers/body-parse.md @@ -43,7 +43,7 @@ return [ ], ], 'middleware_pipeline' => [ - [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class ], + [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class, 'priority' => 100], /* ... */ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, /* ... */ diff --git a/doc/book/helpers/server-url-helper.md b/doc/book/helpers/server-url-helper.md index a6e22a83..18b8faa1 100644 --- a/doc/book/helpers/server-url-helper.md +++ b/doc/book/helpers/server-url-helper.md @@ -92,8 +92,6 @@ $app->pipe(ServerUrlMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ -// /* ... */ -// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, // ['middleware' => ServerUrlMiddleware::class], // /* ... */ // ], @@ -114,8 +112,8 @@ return [ ], ], 'middleware_pipeline' => [ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, ['middleware' => ServerUrlMiddleware::class], + /* ... */ ], ]; ``` diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 7450e7de..5c2a81d3 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -132,6 +132,21 @@ $app->pipe(UrlHelperMiddleware::class); // /* ... */ // ], // ] +// +// Alternately, create a nested middleware pipeline for the routing, UrlHelper, +// and dispatch middleware: +// [ +// 'middleware_pipeline' => [ +// /* ... */ +// ['middleware' => [ +// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// UrlHelperMiddleware::class +// Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, +// ]], +// /* ... */ +// ], +// ] + ``` The following dependency configuration will work for all three when using the @@ -146,8 +161,9 @@ return [ ], ], 'middleware_pipeline' => [ - ['middleware' => UrlHelperMiddleware::class], Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + ['middleware' => UrlHelperMiddleware::class], + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], ] ``` diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index be6d8123..125fb2a1 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -10,6 +10,10 @@ RC6 introduced changes to the following: configuration that simplifies it. - Route result observers are deprecated, and no longer triggered for routing failures. +- Middleware configuration specifications now accept a `priority` key to + guarantee the order of items. If you have defined your middleware pipeline in + multiple files that are then merged, you will need to defined these keys to + ensure order. ## Routing and Dispatch middleware @@ -153,7 +157,8 @@ to follow the guidelines created with RC6. RC6 and later change the configuration to remove the `pre_routing` and `post_routing` keys. However, individual items within the array retain the same -format as middleware inside those keys, namely: +format as middleware inside those keys, with the addition of a new key, +`priority`: ```php [ @@ -162,9 +167,16 @@ format as middleware inside those keys, namely: // Optional: // 'path' => '/path/to/match', // 'error' => true, + // 'priority' => 1, // integer ] ``` +The `priority` key is used to determine the order in which middleware is piped +to the application. Higher integer values are piped earlier, while +lower/negative integer values are piped later; middleware with the same priority +are piped in the order in which they are discovered in the pipeline. The default +priority used is 1. + Additionally, the routing and dispatch middleware now become items in the array; they (or equivalent entries for your own implementations) must be present in your configuration if you want your routed middleware to dispatch! This change @@ -194,18 +206,27 @@ return [ [ 'middleware' => [ Helper\ServerUrlMiddleware::class, - Helper\UrlHelperMiddleware::class, ], + 'priority' => PHP_INT_MAX, ], - // The following is an entry for the routing middleware: - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + // The following is an entry for: + // - routing middleware + // - middleware that reacts to the routing results + // - dispatch middleware + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ] // The following is an entry for the dispatch middleware: - Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, // Place error handling middleware after the routing and dispatch - // middleware. + // middleware, with negative priority. ], ] ``` @@ -213,13 +234,16 @@ return [ To update an existing application: - Promote all `pre_routing` middleware up a level, and remove the `pre_routing` - key. + key. Provide a `priority` value greater than 1. - Add the entries for `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` and `Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE` immediately following any `pre_routing` middleware, and before any - `post_routing` middleware. + `post_routing` middleware; we recommend grouping it per the above example. - Promote all `post_routing` middleware up a level, and remove the - `post_routing` key. + `post_routing` key. Provide a `priority` value less than 1 or negative. +- **If you have `middleware_pipeline` specifications in multiple files**, you + will need to specify `priority` keys for all middleware in order to guarantee + order after merging. Once you have made the above changes, you should no longer receive deprecation notices when running your application. @@ -266,9 +290,14 @@ however, you will need to register it following the routing middleware: [ 'middleware_pipeline' => [ /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - Zend\Expressive\Container\ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE, - Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ] @@ -343,9 +372,14 @@ If you are using the `ApplicationFactory`, alter your configuration: [ 'middleware_pipeline' => [ /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - ['middleware' => MyObserver::class], - Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + ['middleware' => MyObserver::class], + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ] diff --git a/doc/book/usage-examples.md b/doc/book/usage-examples.md index a643538b..b5eadbaf 100644 --- a/doc/book/usage-examples.md +++ b/doc/book/usage-examples.md @@ -469,19 +469,39 @@ Each middleware specified must be in the following form: // optional: 'path' => '/path/to/match', 'error' => true, + 'priority' => 1, // Integer ] ``` -There is one exception to the above rule: to specify the *routing* middleware -that Expressive provides, use the value -`Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE`; this will be -caught by the factory and expanded: +Priority should be an integer, and follows the semantics of +[SplPriorityQueue](http://php.net/SplPriorityQueue): higher numbers indicate +higher priority (top of the queue; executed earliest), while lower numbers +indicated lower priority (bottom of the queue, executed last); *negative values +are low priority*. Items of the same priority are executed in the order in which +they are attached. + +The default priority is 1, and this priority is used by the routing and dispatch +middleware. To indicate that middleware should execute *before* these, use a +priority higher than 1. For error middleware, use a priority less than 1. + +The above specification can be used for all middleware, with one exception: +registration of the *routing* and/or *dispatch* middleware that Expressive +provides. In these cases, use the following constants, which will be caught by +the factory and expanded: + +- `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` for the + routing middleware; this should always come before the dispatch middleware. +- `Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE` for the + dispatch middleware. + +As an example: ```php return [ 'middleware_pipeline' => [ [ /* ... */ ], Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, [ /* ... */ ], ], ]; @@ -495,17 +515,41 @@ return [ > > Typically, you will place any middleware you want to execute on all requests > prior to the routing middleware. This includes utilities for bootstrapping -> the application (such as injection of the `UrlHelper` or `ServerUrlHelper`), +> the application (such as injection of the `ServerUrlHelper`), > utilities for injecting common response headers (such as CORS support), etc. +> Make sure these middleware specifications include the `priority` key, and that +> the value of this key is greater than 1. > > Place *error* middleware *after* the routing middleware. This is middleware > that should only execute if routing fails or routed middleware cannot complete -> the response. +> the response. These specifications should also include the `priority` key, and +> the value of that key for such middleware should be less than 1 or negative. +> +> Use priority to shape the specific workflow you want for your middleware. Middleware items may be any callable, `Zend\Stratigility\MiddlewareInterface` implementation, or a service name that resolves to one of the two. Additionally, you can specify an array of such values; these will be composed in a single `Zend\Stratigility\MiddlewarePipe` instance, allowing layering of middleware. +In fact, you can specify the various `ApplicationFactory::*_MIDDLEWARE` +constants in such arrays as well: + +```php +return [ + 'middleware_pipeline' => [ + [ /* ... */ ], + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + /* ... middleware that introspects routing results ... */ + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], + [ /* ... */ ], + ], +]; +``` The path, if specified, can only be a literal path to match, and is typically used for segregating middleware applications or applying rules to subsets of an diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index 91bc4047..16bb31e0 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -10,6 +10,7 @@ namespace Zend\Expressive\Container; use Interop\Container\ContainerInterface; +use SplPriorityQueue; use Zend\Diactoros\Response\EmitterInterface; use Zend\Expressive\Application; use Zend\Expressive\Exception; @@ -317,7 +318,6 @@ private function injectRoutes(array $routes, Application $app) /** * Given a collection of middleware specifications, pipe them to the application. * - * @todo Remove ROUTE_RESULT_OBSERVER_MIDDLEWARE detection for 1.1 * @param array $collection * @param Application $app * @return bool Flag indicating whether or not any middleware was injected. @@ -325,34 +325,143 @@ private function injectRoutes(array $routes, Application $app) */ private function injectMiddleware(array $collection, Application $app) { - $injections = false; - foreach ($collection as $spec) { - if ($spec === self::ROUTING_MIDDLEWARE) { - $spec = ['middleware' => [$app, 'routeMiddleware']]; + // Create a priority queue from the specifications + $queue = array_reduce( + array_map($this->createCollectionMapper($app), $collection), + $this->createPriorityQueueReducer(), + new SplPriorityQueue() + ); + + $injections = count($queue) > 0; + + foreach ($queue as $spec) { + $path = isset($spec['path']) ? $spec['path'] : '/'; + $error = array_key_exists('error', $spec) ? (bool) $spec['error'] : false; + $pipe = $error ? 'pipeErrorHandler' : 'pipe'; + + $app->{$pipe}($path, $spec['middleware']); + } + + return $injections; + } + + /** + * Create and return the pipeline map callback. + * + * The returned callback has the signature: + * + * + * function ($item) : callable|string + * + * + * It is suitable for mapping pipeline middleware representing the application + * routing o dispatching middleware to a callable; if the provided item does not + * match either, the item is returned verbatim. + * + * @todo Remove ROUTE_RESULT_OBSERVER_MIDDLEWARE detection for 1.1 + * @param Application $app + * @return callable + */ + private function createPipelineMapper(Application $app) + { + return function ($item) use ($app) { + if ($item === self::ROUTING_MIDDLEWARE) { + return [$app, 'routeMiddleware']; } - if ($spec === self::DISPATCH_MIDDLEWARE) { - $spec = ['middleware' => [$app, 'dispatchMiddleware']]; + if ($item === self::DISPATCH_MIDDLEWARE) { + return [$app, 'dispatchMiddleware']; } - if ($spec === self::ROUTE_RESULT_OBSERVER_MIDDLEWARE) { - $spec = ['middleware' => [$app, 'routeResultObserverMiddleware']]; + if ($item === self::ROUTE_RESULT_OBSERVER_MIDDLEWARE) { $r = new \ReflectionProperty($app, 'routeResultObserverMiddlewareIsRegistered'); $r->setAccessible(true); $r->setValue($app, true); + return [$app, 'routeResultObserverMiddleware']; } - if (! is_array($spec) || ! array_key_exists('middleware', $spec)) { - continue; + return $item; + }; + } + + /** + * Create the collection mapping function. + * + * Returns a callable with the following signature: + * + * + * function (array|string $item) : array + * + * + * When it encounters one of the self::*_MIDDLEWARE constants, it passes + * the value to the `createPipelineMapper()` callback to create a spec + * that uses the return value as pipeline middleware. + * + * If the 'middleware' value is an array, it uses the `createPipelineMapper()` + * callback as an array mapper in order to ensure the self::*_MIDDLEWARE + * are injected correctly. + * + * If the 'middleware' value is missing, or not viable as middleware, it + * raises an exception, to ensure the pipeline is built correctly. + * + * @param Application $app + * @return callable + */ + private function createCollectionMapper(Application $app) + { + $pipelineMap = $this->createPipelineMapper($app); + $appMiddlewares = [ + self::ROUTING_MIDDLEWARE, + self::DISPATCH_MIDDLEWARE, + self::ROUTE_RESULT_OBSERVER_MIDDLEWARE + ]; + + return function ($item) use ($app, $pipelineMap, $appMiddlewares) { + if (in_array($item, $appMiddlewares, true)) { + return ['middleware' => $pipelineMap($item)]; } - $path = isset($spec['path']) ? $spec['path'] : '/'; - $error = array_key_exists('error', $spec) ? (bool) $spec['error'] : false; - $pipe = $error ? 'pipeErrorHandler' : 'pipe'; + if (! is_array($item) || ! array_key_exists('middleware', $item)) { + throw new ContainerInvalidArgumentException(sprintf( + 'Invalid pipeline specification received; must be an array containing a middleware ' + . 'key, or one of the ApplicationFactory::*_MIDDLEWARE constants; received %s', + (is_object($item) ? get_class($item) : gettype($item)) + )); + } - $app->{$pipe}($path, $spec['middleware']); - $injections = true; - } - return $injections; + if (! is_callable($item['middleware']) && is_array($item['middleware'])) { + $item['middleware'] = array_map($pipelineMap, $item['middleware']); + } + + return $item; + }; + } + + /** + * Create reducer function that will reduce an array to a priority queue. + * + * Creates and returns a function with the signature: + * + * + * function (SplQueue $queue, array $item) : SplQueue + * + * + * The function is useful to reduce an array of pipeline middleware to a + * priority queue. + * + * @return callable + */ + private function createPriorityQueueReducer() + { + // $serial is used to ensure that items of the same priority are enqueued + // in the order in which they are inserted. + $serial = PHP_INT_MAX; + return function ($queue, $item) use (&$serial) { + $priority = isset($item['priority']) && is_int($item['priority']) + ? $item['priority'] + : 1; + $queue->insert($item, [$priority, $serial--]); + return $queue; + }; } } diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php index ee09d0b7..6ed7bf25 100644 --- a/test/Container/ApplicationFactoryTest.php +++ b/test/Container/ApplicationFactoryTest.php @@ -15,6 +15,7 @@ use Prophecy\Prophecy\ObjectProphecy; use ReflectionFunction; use ReflectionProperty; +use SplQueue; use Zend\Diactoros\Response\EmitterInterface; use Zend\Diactoros\Response\SapiEmitter; use Zend\Expressive\Application; @@ -392,13 +393,8 @@ public function testMiddlewareIsNotAddedIfSpecIsInvalid() $this->injectServiceInContainer($this->container, 'config', $config); + $this->setExpectedException(ContainerException\InvalidArgumentException::class, 'pipeline'); $app = $this->factory->__invoke($this->container->reveal()); - - $r = new ReflectionProperty($app, 'pipeline'); - $r->setAccessible(true); - $pipeline = $r->getValue($app); - - $this->assertCount(0, $pipeline); } public function uncallableMiddleware() @@ -1114,4 +1110,147 @@ public function testUsageOfDeprecatedConfigurationRegistersRouteResultObserverMi $this->assertAttributeSame(true, 'routeResultObserverMiddlewareIsRegistered', $app); } + + public function testFactoryHonorsPriorityOrderWhenAttachingMiddleware() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $pipeline1 = [ [ 'middleware' => clone $middleware, 'priority' => 1 ] ]; + $pipeline2 = [ [ 'middleware' => clone $middleware, 'priority' => 100 ] ]; + $pipeline3 = [ [ 'middleware' => clone $middleware, 'priority' => -100 ] ]; + + $pipeline = array_merge($pipeline3, $pipeline1, $pipeline2); + $config = [ 'middleware_pipeline' => $pipeline ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertSame($pipeline2[0]['middleware'], $pipeline->dequeue()->handler); + $this->assertSame($pipeline1[0]['middleware'], $pipeline->dequeue()->handler); + $this->assertSame($pipeline3[0]['middleware'], $pipeline->dequeue()->handler); + } + + public function testMiddlewareWithoutPriorityIsGivenDefaultPriorityAndRegisteredInOrderReceived() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $pipeline1 = [ [ 'middleware' => clone $middleware ] ]; + $pipeline2 = [ [ 'middleware' => clone $middleware ] ]; + $pipeline3 = [ [ 'middleware' => clone $middleware ] ]; + + $pipeline = array_merge($pipeline3, $pipeline1, $pipeline2); + $config = [ 'middleware_pipeline' => $pipeline ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $pipeline = $r->getValue($app); + + $this->assertSame($pipeline3[0]['middleware'], $pipeline->dequeue()->handler); + $this->assertSame($pipeline1[0]['middleware'], $pipeline->dequeue()->handler); + $this->assertSame($pipeline2[0]['middleware'], $pipeline->dequeue()->handler); + } + + public function testRoutingAndDispatchMiddlewareUseDefaultPriority() + { + // @codingStandardsIgnoreStart + $middleware = function ($request, $response, $next) {}; + // @codingStandardsIgnoreEnd + + $pipeline = [ + [ 'middleware' => clone $middleware, 'priority' => -100 ], + ApplicationFactory::ROUTING_MIDDLEWARE, + [ 'middleware' => clone $middleware, 'priority' => 1 ], + [ 'middleware' => clone $middleware ], + ApplicationFactory::DISPATCH_MIDDLEWARE, + [ 'middleware' => clone $middleware, 'priority' => 100 ], + ]; + + $config = [ 'middleware_pipeline' => $pipeline ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $test = $r->getValue($app); + + $this->assertSame($pipeline[5]['middleware'], $test->dequeue()->handler); + $this->assertSame([ $app, 'routeMiddleware' ], $test->dequeue()->handler); + $this->assertSame($pipeline[2]['middleware'], $test->dequeue()->handler); + $this->assertSame($pipeline[3]['middleware'], $test->dequeue()->handler); + $this->assertSame([ $app, 'dispatchMiddleware' ], $test->dequeue()->handler); + $this->assertSame($pipeline[0]['middleware'], $test->dequeue()->handler); + } + + public function specMiddlewareContainingRoutingAndOrDispatchMiddleware() + { + // @codingStandardsIgnoreStart + return [ + 'routing-only' => [[['middleware' => [ApplicationFactory::ROUTING_MIDDLEWARE]]]], + 'dispatch-only' => [[['middleware' => [ApplicationFactory::DISPATCH_MIDDLEWARE]]]], + 'both-routing-and-dispatch' => [[['middleware' => [ApplicationFactory::ROUTING_MIDDLEWARE, ApplicationFactory::DISPATCH_MIDDLEWARE]]]], + ]; + // @codingStandardsIgnoreEnd + } + + /** + * @dataProvider specMiddlewareContainingRoutingAndOrDispatchMiddleware + */ + public function testRoutingAndDispatchMiddlewareCanBeComposedWithinArrayStandardSpecification($pipeline) + { + $expected = $pipeline[0]['middleware']; + $config = [ 'middleware_pipeline' => $pipeline ]; + $this->injectServiceInContainer($this->container, 'config', $config); + + $app = $this->factory->__invoke($this->container->reveal()); + + $r = new ReflectionProperty($app, 'pipeline'); + $r->setAccessible(true); + $appPipeline = $r->getValue($app); + + $this->assertEquals(1, count($appPipeline)); + + $innerMiddleware = $appPipeline->dequeue()->handler; + $this->assertInstanceOf(MiddlewarePipe::class, $innerMiddleware); + + $r = new ReflectionProperty($innerMiddleware, 'pipeline'); + $r->setAccessible(true); + $innerPipeline = $r->getValue($innerMiddleware); + $this->assertInstanceOf(SplQueue::class, $innerPipeline); + + $this->assertEquals( + count($expected), + $innerPipeline->count(), + sprintf('Expected %d items in pipeline; received %d', count($expected), $innerPipeline->count()) + ); + + foreach ($innerPipeline as $index => $route) { + $innerPipeline[$index] = $route->handler; + } + + foreach ($expected as $type) { + switch ($type) { + case ApplicationFactory::ROUTING_MIDDLEWARE: + $middleware = [$app, 'routeMiddleware']; + break; + case ApplicationFactory::DISPATCH_MIDDLEWARE: + $middleware = [$app, 'dispatchMiddleware']; + break; + default: + $this->fail('Unexpected value in pipeline passed from data provider'); + } + $this->assertContains($middleware, $innerPipeline); + } + } } From 38cedd5393a1e5b902d778f06b482158c89868e8 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sat, 16 Jan 2016 16:30:51 -0600 Subject: [PATCH 10/18] Incorporated feedback from contributors --- ...ting-locale-depending-routing-parameter.md | 40 +++---- ...etting-locale-without-routing-parameter.md | 50 +++++---- doc/book/helpers/url-helper.md | 2 + doc/book/migration/rc-to-v1.md | 100 ++++++++++++++++-- 4 files changed, 142 insertions(+), 50 deletions(-) diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index 99762117..2303452e 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -6,7 +6,7 @@ In this recipe we will concentrate on using a routing parameter. > ## Routing parameters > -> Using the approach in this chapter requires that you add a `/:lang` (or +> Using the approach in this chapter requires that you add a `/:locale` (or > similar) segment to each and every route that can be localized, and, depending > on the router used, may also require additional options for specifying > constraints. If the majority of your routes are localized, this will become @@ -16,9 +16,9 @@ In this recipe we will concentrate on using a routing parameter. ## Setting up the route If you want to set the locale depending on an routing parameter, you first have -to add a language parameter to each route that requires localization. +to add a locale parameter to each route that requires localization. -In this example we use the `lang` parameter, which should consist of two +In this example we use the `locale` parameter, which should consist of two lowercase alphabetical characters: ```php @@ -38,23 +38,23 @@ return [ 'routes' => [ [ 'name' => 'home', - 'path' => '/:lang', + 'path' => '/:locale', 'middleware' => Application\Action\HomePageAction::class, 'allowed_methods' => ['GET'], 'options' => [ 'constraints' => [ - 'lang' => '[a-z]{2}', + 'locale' => '[a-z]{2}', ], ], ], [ 'name' => 'contact', - 'path' => '/:lang/contact', + 'path' => '/:locale/contact', 'middleware' => Application\Action\ContactPageAction::class, 'allowed_methods' => ['GET'], 'options' => [ 'constraints' => [ - 'lang' => '[a-z]{2}', + 'locale' => '[a-z]{2}', ], ], ], @@ -71,13 +71,13 @@ return [ > ```php > [ > 'name' => 'home', -> 'path' => '/{lang}', +> 'path' => '/{locale}', > 'middleware' => Application\Action\HomePageAction::class, > 'allowed_methods' => ['GET'], > 'options' => [ > 'constraints' => [ > 'tokens' => [ -> 'lang' => '[a-z]{2}', +> 'locale' => '[a-z]{2}', > ], > ], > ], @@ -89,7 +89,7 @@ return [ > ```php > [ > 'name' => 'home', -> 'path' => '/{lang:[a-z]{2}}', +> 'path' => '/{locale[a-z]{2}}', > 'middleware' => Application\Action\HomePageAction::class, > 'allowed_methods' => ['GET'], > ] @@ -105,7 +105,7 @@ To make sure that you can setup the locale after the routing has been processed, you need to implement localization middleware that acts on the route result, and registered in the pipeline immediately following the routing middleware. -Such a `LocalizationObserver` class could look similar to this: +Such a `LocalizationMiddleware` class could look similar to this: ```php namespace Application\I18n; @@ -113,20 +113,12 @@ namespace Application\I18n; use Locale; use Zend\Expressive\Router\RouteResult; -class LocalizationObserver +class LocalizationMiddleware { public function __invoke($request, $response, $next) { - $result = $request->getAttribute(RouteResult::class, false); - if (! $result) { - return $next($request, $response); - } - - $matchedParams = $result->getMatchedParams(); - - $lang = isset($matchedParams['lang']) ? $matchedParams['lang'] : 'de_DE'; - Locale::setDefault($matchedParams['lang']); - + $locale = $request->getAttribute('locale', 'de_DE'); + Locale::setDefault($locale); return $next($request, $response); } } @@ -140,7 +132,7 @@ middleware: return [ 'dependencies' => [ 'invokables' => [ - LocalizationObserver::class => LocalizationObserver::class, + LocalizationMiddleware::class => LocalizationMiddleware::class, /* ... */ ], /* ... */ @@ -150,7 +142,7 @@ return [ [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - [ 'middleware' => LocalizationObserver::class ], + [ 'middleware' => LocalizationMiddleware::class ], Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], 'priority' => 1, diff --git a/doc/book/cookbook/setting-locale-without-routing-parameter.md b/doc/book/cookbook/setting-locale-without-routing-parameter.md index 81cd0f26..c69faf58 100644 --- a/doc/book/cookbook/setting-locale-without-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-without-routing-parameter.md @@ -15,15 +15,15 @@ requiring any changes to existing routes. > with a required routing parameter; this approach is described in the > ["Setting a locale based on a routing parameter" recipe](setting-locale-depending-routing-parameter.md). -## Setup a middleware to extract the language from the URI +## Setup a middleware to extract the locale from the URI -First, we need to setup middleware that extracts the language param directly +First, we need to setup middleware that extracts the locale param directly from the request URI's path. If if doesn't find one, it sets a default. -If it does find one, it uses the language to setup the locale. It also: +If it does find one, it uses the value to setup the locale. It also: -- amends the request with a truncated path (removing the language segment). -- adds the langauge segment as the base path of the `UrlHelper`. +- amends the request with a truncated path (removing the locale segment). +- adds the locale segment as the base path of the `UrlHelper`. ```php namespace Application\I18n; @@ -31,7 +31,7 @@ namespace Application\I18n; use Locale; use Zend\Expressive\Helper\UrlHelper; -class SetLanguageMiddleware +class SetLocaleMiddleware { private $helper; @@ -47,14 +47,14 @@ class SetLanguageMiddleware $path = $uri->getPath(); - if (! preg_match('#^/(?P[a-z]{2})/#', $path, $matches) { + if (! preg_match('#^/(?P[a-z]{2})/#', $path, $matches) { Locale::setDefault('de_DE'); return $next($request, $response); } - $lang = $matches['lang']; - Locale::setDefault($lang); - $this->helper->setBasePath($lang); + $locale = $matches['locale']; + Locale::setDefault($locale); + $this->helper->setBasePath($locale); return $next( $request->withUri( @@ -66,7 +66,7 @@ class SetLanguageMiddleware } ``` -Then you will need a factory for the `SetLanguageMiddleware` to inject the +Then you will need a factory for the `SetLocaleMiddleware` to inject the `UrlHelper` instance. ```php @@ -75,18 +75,18 @@ namespace Application\I18n; use Interop\Container\ContainerInterface; use Zend\Expressive\Helper\UrlHelper; -class SetLanguageMiddlewareFactory +class SetLocaleMiddlewareFactory { public function __invoke(ContainerInterface $container) { - return new SetLanguageMiddleware( + return new SetLocaleMiddleware( $container->get(UrlHelper::class) ); } } ``` -Afterwards, you need to configure the `SetLanguageMiddleware` in your +Afterwards, you need to configure the `SetLocaleMiddleware` in your `/config/autoload/middleware-pipeline.global.php` file so that it is executed on every request. @@ -95,8 +95,8 @@ return [ 'dependencies' => [ /* ... */ 'factories' => [ - Application\I18n\SetLanguageMiddleware::class => - Application\I18n\SetLanguageMiddlewareFactory::class, + Application\I18n\SetLocaleMiddleware::class => + Application\I18n\SetLocaleMiddlewareFactory::class, /* ... */ ], ] @@ -104,13 +104,23 @@ return [ 'middleware_pipeline' => [ [ 'middleware' => [ - Application\I18n\SetLanguageMiddleware::class, + Application\I18n\SetLocaleMiddleware::class, /* ... */ ], 'priority' => 1000, ], - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + /* ... */ + + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + /* ... */ + ], + 'priority' => 1, + ], /* ... */ ], @@ -119,7 +129,7 @@ return [ ## Url generation in the view -Since the `UrlHelper` has the language set as a base path, you don't need +Since the `UrlHelper` has the locale set as a base path, you don't need to worry about generating URLs within your view. Just use the helper to generate a URL and it will do the rest. @@ -134,7 +144,7 @@ generate a URL and it will do the rest. ## Redirecting within your middleware -If you want to add the language parameter when creating URIs within your +If you want to add the locale parameter when creating URIs within your action middleware, you just need to inject the `UrlHelper` into your middleware and use it for URL generation: diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 5c2a81d3..63f48190 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -122,6 +122,7 @@ use Zend\Expressive\Helper\UrlHelperMiddleware; // Programmatically: $app->pipeRoutingMiddleware(); $app->pipe(UrlHelperMiddleware::class); +$app->pipeDispatchMiddleware(); // Or use configuration: // [ @@ -129,6 +130,7 @@ $app->pipe(UrlHelperMiddleware::class); // /* ... */ // Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, // ['middleware' => UrlHelperMiddleware::class], +// Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, // /* ... */ // ], // ] diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index 125fb2a1..1aa1c045 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -146,9 +146,23 @@ return [ //], ], ], -] +]; ``` +The following changes have been made: + +- The concept of `pre_routing` and `post_routing` have been deprecated, and will + be removed starting with the 1.1 version. A single middleware pipeline is now + provided, though *any individual specification can also specify an array of + middleware*. +- **The routing and dispatch middleware must now be added to your configuration + for them to be added to your application.** +- Middleware specifications can now optionally provide a `priority` key, with 1 + being the default. High integer priority indicates earlier execution, while + low/negative integer priority indicates later execution. Items with the same + priority are executed in the order they are registered. Priority is now how + you can indicate the order in which middleware should execute. + ### Impact While the configuration from RC5 and earlier will continue to work, it will @@ -227,14 +241,21 @@ return [ // Place error handling middleware after the routing and dispatch // middleware, with negative priority. + // [ + // 'middleware' => [ + // ], + // 'priority' => -1000, + // ], ], -] +]; ``` To update an existing application: - Promote all `pre_routing` middleware up a level, and remove the `pre_routing` - key. Provide a `priority` value greater than 1. + key. Provide a `priority` value greater than 1. We recommend having a single + middleware specification with an array of middleware that represents the "pre + routing" middleware. - Add the entries for `Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE` and `Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE` immediately following any `pre_routing` middleware, and before any @@ -243,10 +264,77 @@ To update an existing application: `post_routing` key. Provide a `priority` value less than 1 or negative. - **If you have `middleware_pipeline` specifications in multiple files**, you will need to specify `priority` keys for all middleware in order to guarantee - order after merging. + order after merging. We recommend having a single middleware specification + with an array of middleware that represents the "post routing" middleware. + +As an example, consider the following application configuration: + +```php +return [ + 'middleware_pipeline' => [ + 'pre_routing' => [ + [ + 'middleware' => [ + Zend\Expressive\Helper\ServerUrlMiddleware::class, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + ], + ], + [ 'middleware' => DebugToolbarMiddleware::class ], + [ + 'middleware' => ApiMiddleware::class, + 'path' => '/api, + ], + ], + + 'post_routing' => [ + ['middleware' => NotFoundMiddleware::class, 'error' => true], + ], + ], +]; +``` + +This would be rewritten to the following to work with RC6 and later: + +```php +return [ + 'middleware_pipeline' => [ + [ + 'middleware' => [ + Zend\Expressive\Helper\ServerUrlMiddleware::class, + DebugToolbarMiddleware::class, + ], + 'priority' => PHP_INT_MAX, + ], + [ + 'middleware' => ApiMiddleware::class, + 'path' => '/api, + 'priority' => 100, + ], + + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], + + [ + 'middleware' => [ + NotFoundMiddleware::class, + ], + 'error' => true, + 'priority' => -1000, + ], + ], +] +``` -Once you have made the above changes, you should no longer receive deprecation -notices when running your application. +Note in the above example the various groupings. By grouping middleware by +priority, you can simplify adding new middleware, particularly if you know it +should execute before routing, or as error middleware, or between routing and +dispatch. ## Route result observer deprecation From 7231a710d98fa12f9f321118087161b5097ccf91 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sat, 16 Jan 2016 16:46:20 -0600 Subject: [PATCH 11/18] Update server-url docs - Pipe with priority - Pipe before routing middleware --- doc/book/helpers/server-url-helper.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/book/helpers/server-url-helper.md b/doc/book/helpers/server-url-helper.md index 18b8faa1..e2a2cb8a 100644 --- a/doc/book/helpers/server-url-helper.md +++ b/doc/book/helpers/server-url-helper.md @@ -86,13 +86,14 @@ routing middleware: use Zend\Expressive\Helper\ServerUrlMiddleware; // Programmatically: -$app->pipeRoutingMiddleware(); $app->pipe(ServerUrlMiddleware::class); +$app->pipeRoutingMiddleware(); +$app->pipeDispatchMiddleware(); // Or use configuration: // [ // 'middleware_pipeline' => [ -// ['middleware' => ServerUrlMiddleware::class], +// ['middleware' => ServerUrlMiddleware::class, 'priority' => PHP_INT_MAX], // /* ... */ // ], // ] @@ -112,7 +113,7 @@ return [ ], ], 'middleware_pipeline' => [ - ['middleware' => ServerUrlMiddleware::class], + ['middleware' => ServerUrlMiddleware::class, 'priority' => PHP_INT_MAX], /* ... */ ], ]; From 178dd19dd52be1f90bfd174e62765be3a7efffc6 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sat, 16 Jan 2016 16:57:43 -0600 Subject: [PATCH 12/18] Minor documentation changes based on review - Updated the url-helper configuration examples. - Updated the `ApplicationFactory` class docblock to reference the `priority` key of specifications. --- doc/book/helpers/url-helper.md | 35 +++++++++++++++++++++++----- src/Container/ApplicationFactory.php | 15 +++++++++--- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 63f48190..52f979a3 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -140,11 +140,14 @@ $app->pipeDispatchMiddleware(); // [ // 'middleware_pipeline' => [ // /* ... */ -// ['middleware' => [ -// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, -// UrlHelperMiddleware::class -// Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, -// ]], +// [ +// 'middleware' => [ +// Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, +// UrlHelperMiddleware::class +// Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, +// ], +// 'priority' => 1, +// ], // /* ... */ // ], // ] @@ -167,7 +170,27 @@ return [ ['middleware' => UrlHelperMiddleware::class], Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], -] +]; + +// OR: +return [ + 'dependencies' => [ + 'factories' => [ + UrlHelper::class => UrlHelperFactory::class, + UrlHelperMiddleware::class => UrlHelperMiddlewareFactory::class, + ], + ], + 'middleware_pipeline' => [ + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], + ], +]; ``` > #### Skeleton configures helpers diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php index 16bb31e0..9f92f637 100644 --- a/src/Container/ApplicationFactory.php +++ b/src/Container/ApplicationFactory.php @@ -92,6 +92,7 @@ * // optional: * 'path' => '/path/to/match', * 'error' => true, + * 'priority' => 1, // integer * ] * * @@ -101,12 +102,20 @@ * Omitting `error` or setting it to a non-true value is the default, * indicating the middleware is standard middleware. * - * Middleware are pipe()'d to the application instance in the order in which - * they appear. + * `priority` is used to shape the order in which middleware is piped to the + * application. Values are integers, with high values having higher priority + * (piped earlier), and low/negative values having lower priority (piped last). + * Default priority if none is specified is 1. Middleware with the same + * priority are piped in the order in which they appear. * * Middleware piped may be either callables or service names. If you specify * the middleware's `error` flag as `true`, the middleware will be piped using - * Application::pipeErrorHandler() instead of Application::pipe(). + * `Application::pipeErrorHandler()` instead of `Application::pipe()`. + * + * Additionally, you can specify an array of callables or service names as + * the `middleware` value of a specification. Internally, this will create + * a `Zend\Stratigility\MiddlewarePipe` instance, with the middleware + * specified piped in the order provided. */ class ApplicationFactory { From a79eccf26bc32effbaece358c72b05bb48e6ea73 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 18 Jan 2016 10:09:20 -0600 Subject: [PATCH 13/18] Updated features flow diagram and narrative. Removes the `pre_routing` and `post_routing` verbiage. --- doc/book/features.md | 38 +++++++++++++++++++++++-------- doc/book/images/architecture.dia | Bin 1279 -> 1405 bytes doc/book/images/architecture.png | Bin 35521 -> 41902 bytes doc/book/images/architecture.svg | 19 +++++++++++----- doc/book/images/architecture.xcf | Bin 135993 -> 148938 bytes 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/doc/book/features.md b/doc/book/features.md index e8dfc8fe..9f39ea76 100644 --- a/doc/book/features.md +++ b/doc/book/features.md @@ -98,8 +98,20 @@ argument to act on. > - Responses are returned back *through* the pipeline, in reverse order of > traversal. -The `Application` allows for "pre routing" middleware, routing middleware (and -the routed middleware it dispatches), and "post routing" middleware. +The `Application` allows arbitrary middleware to be injected, with each being +executed in the order in which they are attached; returning a response from +middleware prevents any middleware attached later from executing. + +You can attach middleware manually, in which case the pipeline is executed in +the order of attachment, or use configuration. When you use configuration, you +will specify a priority integer to dictate the order in which middleware should +be attached. Middleware specifying high integer prioritiess are attached (and +thus executed) earlier, while those specifying lower and/or negative integers +are attached later. The default priority is 1. + +Expressive provides a default implementation of "routing" and "dispatch" +middleware, which you either attach to the middleware pipeline manually, or via +configuration. Routing within Expressive consists of decomposing the request to match it to middleware that can handle that given request. This typically consists of a @@ -109,15 +121,22 @@ combination of matching the requested URI path along with allowed HTTP methods: - map a POST request to the path `/contact/process` to the `HandleContactMiddleware` - etc. +Dispatching is simply the act of calling the middleware mapped by routing. The +two events are modeled as separate middleware to allow you to act on the results +of routing before attempting to dispatch the mapped middleware; this can be +useful for implementing route-based authentication or validation. + The majority of your application will consist of routing rules that map to routed middleware. -"Pre routing" middleware is middleware that you wish to execute for every -request. These might include: +Middleware piped to the application earlier than routing should be middleware +that you wish to execute for every request. These might include: -- authentication +- bootstrapping - parsing of request body parameters - addition of debugging tools +- embedded Expressive applications that you want to match at a given literal + path - etc. Such middleware may decide that a request is invalid, and return a response; @@ -125,19 +144,20 @@ doing so means no further middleware will be executed! This is an important feature of middleware architectures, as it allows you to define application-specific workflows optimized for performance, security, etc. -"Post routing" middleware will execute in one of two conditions: +Middleware piped to the application after the routing and dispatch middleware +will execute in one of two conditions: - routing failed - routed middleware called on the next middleware instead of returning a response. -As such, the largest use case for post routing middleware is for error handling. +As such, the largest use case for such middleware is for error handling. One possibility is for [providing custom 404 handling](cookbook/custom-404-page-handling.md), or handling application-specific error conditions (such as authentication or authorization failures). Another possibility is to provide post-processing on the response before -returning it. However, this is typically better handled via pre-routing -middleware, by capturing the response before returning it: +returning it. However, this is typically better handled via middleware piped +early, by capturing the response before returning it: ```php function ($request, $response, $next) diff --git a/doc/book/images/architecture.dia b/doc/book/images/architecture.dia index 7c21e11d6acb8c350756f72b8f033214694058c1..e96864a5b7cf7ed4a530a2fa8f59a6f4b1da6b26 100644 GIT binary patch literal 1405 zcmV-@1%mn?iwFP!000021MQqkbE7yAfbaPg6uDxIB*bGf&Q#4|PgAvr-M!4kB^w26 z2ZKwl<%vZFOrmHB4WKb5ILbIf8CL(q|!_%4xb;t zQ6VUQEHA4%xYc*;Vow?pbHYOwAKJ>sCn^U6ni%={<^T^P3%cliT&_1axvjbAw&wV4 z&9QX6NVwpHi9?fsqJKm z2t0}@zP?0+OBT?yAAw@5@~}Fy*1Wf?b%1$eQv>{nu@&)~XM<$~YRC z3ikTGw=*Wm7JLsNK7S6_f${^k!bXRz z#J$r!?j7lIkGFB}NsoI+O{>(js&CBP9@ez5{CSnVv!>=0wV#jL%ZrU{bU&zh zJ)q{wN6pzm&6kgwt58#@^^Ka}18Tl})VwWf5XeW(SEwn}dVeU|L-zrc|5Oz2JQdBK z)yQ6(1Bx}pTJNm&(Sd;E*ELwMhHlk?fF!bp6l<5UW?2rJ+P@=f-&y#bgx^?9caQ_` z$aU^S0FEKEyk&rSo(GMxzWLQH9CAS0mOd_K<+N%_)A~DB>Uw<~aob;2EexQkf=kJkCCc zOY#b8$Qvyk&mwiY*tdMFsa`R6m(1BcFo&fxXV;m-H)0MqFozX$in;5V!#yzPN@vcg zGw0rjIk$m1S23rUyPi3xkNZ_eE_69>1}K|n%6nAEH(41g+23cTyV>)iYTT8G|6+*vtxz;|=- z@~_I2)MYf@qAJx~J9Zw}swUkn8l9U}Q0w@(2n|@TNN9tG^nz4(?AZBVsvIdWdIbY^ z?koXNE*T*IzLx<|eE}gO;}$CjumO(z5u2?hYYM#U!2><4A^4^(f4ST@EpLZB-}G*> zhEVtID0=PPSMYso_gt^ceRi z`d@$kHgxo_FHc`QMn1(aUy!91e}YHJyNSM#a`kCE-fT9bU>gw`ic!GUqlk|GAwfXK zvC&w6dD65)2Og0mIhG$KQVKR(OR90Qq!WEc=0ANAuDPdYR=M3g3__v3kzk@fzowf$ z&fSblelGZ45ueTkB|ocOgLre@Cazah9J^hv!idEta=SV)sqmBR4{UO)C^q8$%ky_C zB^A%vW#tD8f9Ec?GLT#n;WK_3D;}R98w_KFk(+E5bT9&jjdtU5d$`$s!v*&ZC+-`L zMblL%q##V5hMa|AKnc&HO0lNR<3w{3#1gxd7HsbQl}Q=aT>mQxqH7Y!4)2M(m0n*k z?=rk4iwZ9r=E=qMUD0r5-nWXpzp;qT0($Q18JC^8-*)5v_}fVu=8sY&s z&i=G^RQaCGj$MoR;tBTyx^q7QcrXO<{&TV$7-$5bZ8i%@1G=2fL%~mH37w>r`cJFK zX-*n%R>y@f8!HvIRVq*` zR8R;N+No0Gol2Pkl`4g{N|`qEJqv|LMctKy5d_%=O&36D!G_@&kH>#n?o?-b6}BnF z_TzA7wCBuF&KVrJJIqYwm>C}nvk$6!)L!?da^0gs_m*qi}D`ekuK<9`gn(jP1(~Xa3|ED%I8f$A#3Q7SOdU9 zbMseZ?FaLJ5dS;l^Z+@yL$38Q3TO>fP9tN$9LF&R**wzC@01`5HtncFE~e?_U!T;O z^KSil>!N{GGPpT|P%z3**rR;C%<(u0UYvs$&NZwDWG%y3+p@fLwRUmoEg^zfz|%-> z16rMR(}1W#7h)4r&TgTT|R*J(h(W zBdIA6LT7+l6qR|%`856>uBj`irfxJ$JXY#_bsYKJLf@Xbhhz?Qz#LZ2oLOcL--$U~ z!yN9J>zTWqIkN-iY~{>ZW#;TVF=y8>XZOtY%-zl$?&9~Vr53t`w*k!7sAa!Z2ZoW- zryjf7pPfzBrUbm6j&A8_z0?Kb#sayHj%)@v7{Zi?&k*nVXnG&@dOFIvoi!N3zAg3O z-40&sg)gwN^seF#B-laMPQXk4k(N;h(OtSyDYbLwA?kb5!=ll8p$clOkE_ry${h*q p(NNxy%88vj|G#QSJs1^WWbf1e>AyUc{>cCG^e z#8o`7$928q6Zj9$Oi5k_g2w#$T%Y|B>>+Sg&~*i8jAFjAp4?0H1UvEF6jfyL=dj2~ z#IERB=o3O9w;_r$kDq%^Y)*UmJRiP5?~e9Z@F^->)%EANadh=Dg{4NQ{}&zoKr>F8 z%+Tqd6y>MxSdP&v_MzV{F25yCFxiz4GYwJL=T+G7-sa-yYQTF!cKgv21wP#$%<(4| zydUp$Gh2L6=IcH?@mz{p>Pd$8OTgm$B~Y&Y{=xtM{lX>0`Ki`>n`yxO0~Y&E@(pMZ z8iyEXeuCvyKfZr@r?^<#n?q_4Jk35BovsBLk5aZNdhldA5k&jQC{J-W5?-XmmA z;$(_oYJTEJqJic1YZJHuQV+GAJ1y(h_mQFYry~UULK9MgeGiMn?%kaDiHy*Fer$ci zWzCz;e~uw%)FU3kx){C*QFh%5z3x+eBYk3=do19vs8Rl#CnudJ?%4Gt990}{EJcQu zkJxp@pKvUj7z4O#JTmMPm0|VyoB5nGXbJ6;Xu}+7@g3T^d+OIeVT&}mb7oC$>y302 zp;NO@9D`aRcBqJ=eQm=HK^#x&9B0RDsY;gxB{#8s3*G&pM! zH%1WmXpj3PD5HWm%1MP?MU1coA9_vG+}=X=iVB_bt6vpIP8h*zRBy!2 zZ>~V6Qnqmx{V5Ihldp(hGg!K#gLhoNfqTi!LsQi~TvVp7Jo@X@dy<$*^ynNtrsSRs zn_XXV^-Hfc4CoFmaun+uc5*YD=ySLZ-SB5um|Va1BfRK3HUTzcl_1i2RO`FVoat!u zB~~BX5e*`7znVnFaQ;DKrQ$fuz%o?dAenB>j3idbJ=m?VLy2Lewh+E3flw-~ot#c| zcdF}}QQ2oRi>c{??v_X}LU|)c4VPo#V=4MJGxish+FnoCHa1lL4lK07odjiQA5-zJh&YgYEP-L3^5; zLNjKb4Q+7kZ=UtJjTKdVlMAO3IUf%Xv*=~qHm|goj(T1%|3=$?!8y4m6Z$nlRdq7n zao&-hCfVhJVJ;u%QRPi|e{WHs!_B@)``LD{v*NV?p=-Yb%dSs-%n}1VZz4HtmT=BB zGMFg(5Q4&KbWPTga!vM}`5r%bBW!CX-d%EH0c~FN@oGKd@{7gz~-;s`uP>7E6tz%xGre_5+zm?=tRE5 zt~aaK-4RbRr>HA5np?Q5Q1fq+>Kfn zJ!r+7N_U$zL{haeo-zH^uK7L2Mz=#Ai3z6QcUfLcKIsYxY$yTA`MpAl!KwB6`YT1V z+7IvZX!8Ex2_X374r& zI8A5>dqCO4?e+3GI@b(_Rbg%vR%#7#QH+Ga9348(Wdh#-4Lx+$T2Y~YyRd2HJ6wLw z=zMAW!Fs|bl4mYs0})xP>JR&Qarg2xZ@ON0wLO1Rvni-(Ew$N8%#6Pmx~At(FTAgm z(&05Z≻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 literal 35521 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 - + + + @@ -11,8 +13,7 @@ - - + @@ -25,9 +26,15 @@ - - - + + + + + + + + + diff --git a/doc/book/images/architecture.xcf b/doc/book/images/architecture.xcf index aa4719374b6aec58f6fc2617ea21cd17f2a2d059..0a0a4925407d5a6cb23b3e350d41597576936e13 100644 GIT binary patch literal 148938 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 delta 13406 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 From 0ada59095d0b3f7c5aa65e45aec410c7b06af759 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 18 Jan 2016 10:28:13 -0600 Subject: [PATCH 14/18] Demonstrate using keys for middleware specs By using keys, you can have multiple configurations that are related, and which will merge into a single pipeline. This also allows us to have a common "routing" key for the routing middleware, which makes it easier to identify and replace if desired. --- doc/book/container/factories.md | 6 ++++- doc/book/cookbook/custom-404-page-handling.md | 9 +++++++- doc/book/cookbook/route-specific-pipeline.md | 2 +- ...ting-locale-depending-routing-parameter.md | 1 + ...etting-locale-without-routing-parameter.md | 3 +-- doc/book/cookbook/using-a-base-path.md | 9 +++++++- .../cookbook/using-zend-form-view-helpers.md | 9 +++++++- doc/book/helpers/body-parse.md | 9 +++++++- doc/book/helpers/url-helper.md | 4 ++-- doc/book/migration/rc-to-v1.md | 22 ++++++++++++++----- doc/book/usage-examples.md | 12 +++++++++- 11 files changed, 69 insertions(+), 17 deletions(-) diff --git a/doc/book/container/factories.md b/doc/book/container/factories.md index 6eae1997..dee32539 100644 --- a/doc/book/container/factories.md +++ b/doc/book/container/factories.md @@ -35,7 +35,7 @@ instance. When the `config` service is present, the factory can utilize several keys in order to seed the `Application` instance: -- `middleware_pipeline` can be used to seed pre- and/or post-routing middleware: +- `middleware_pipeline` can be used to seed the middleware pipeline: ```php 'middleware_pipeline' => [ @@ -75,6 +75,10 @@ order to seed the `Application` instance: granular priority values to specify the order in which middleware should be piped to the application. + You *can* specify keys for each middleware specification. These will be + ignored by the factory, but can be useful when merging several configurations + into one for the application. + - `routes` is used to define routed middleware. The value must be an array, consisting of arrays defining each middleware: diff --git a/doc/book/cookbook/custom-404-page-handling.md b/doc/book/cookbook/custom-404-page-handling.md index 96288555..04ea4ade 100644 --- a/doc/book/cookbook/custom-404-page-handling.md +++ b/doc/book/cookbook/custom-404-page-handling.md @@ -92,7 +92,14 @@ configuration, after the dispatch middleware: ```php 'middleware_pipeline' => [ /* ... */ - Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + 'routing' => [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], [ 'middleware' => 'Application\NotFound', 'priority' => -1, diff --git a/doc/book/cookbook/route-specific-pipeline.md b/doc/book/cookbook/route-specific-pipeline.md index f13c621c..09cb551c 100644 --- a/doc/book/cookbook/route-specific-pipeline.md +++ b/doc/book/cookbook/route-specific-pipeline.md @@ -168,7 +168,7 @@ answer is that the syntax is exactly the same! ```php return [ 'middleware_pipeline' => [ - [ + 'api' => [ 'path' => '/api', 'middleware' => [ 'AuthenticationMiddleware', diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index 2303452e..50d3b6a9 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -142,6 +142,7 @@ return [ [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Helper\UrlHelperMiddleware::class, [ 'middleware' => LocalizationMiddleware::class ], Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], diff --git a/doc/book/cookbook/setting-locale-without-routing-parameter.md b/doc/book/cookbook/setting-locale-without-routing-parameter.md index c69faf58..4ceaf06b 100644 --- a/doc/book/cookbook/setting-locale-without-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-without-routing-parameter.md @@ -112,12 +112,11 @@ return [ /* ... */ - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, Zend\Expressive\Helper\UrlHelperMiddleware::class, Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, - /* ... */ ], 'priority' => 1, ], diff --git a/doc/book/cookbook/using-a-base-path.md b/doc/book/cookbook/using-a-base-path.md index 64fa73dd..05810afa 100644 --- a/doc/book/cookbook/using-a-base-path.md +++ b/doc/book/cookbook/using-a-base-path.md @@ -108,7 +108,14 @@ return [ 'middleware_pipeline' => [ [ 'middleware' => [ Blast\BaseUrl\BaseUrlMiddleware::class ], 'priority' => 1000 ], /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + 'routing' => [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ]; diff --git a/doc/book/cookbook/using-zend-form-view-helpers.md b/doc/book/cookbook/using-zend-form-view-helpers.md index bbeb305c..bc104952 100644 --- a/doc/book/cookbook/using-zend-form-view-helpers.md +++ b/doc/book/cookbook/using-zend-form-view-helpers.md @@ -226,7 +226,14 @@ return [ 'middleware_pipeline' => [ ['middleware' => Your\Application\FormHelpersMiddleware::class, 'priority' => 1000], /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + 'routing' => [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Zend\Expressive\Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ]; diff --git a/doc/book/helpers/body-parse.md b/doc/book/helpers/body-parse.md index bddc6e34..78e8a136 100644 --- a/doc/book/helpers/body-parse.md +++ b/doc/book/helpers/body-parse.md @@ -45,7 +45,14 @@ return [ 'middleware_pipeline' => [ [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class, 'priority' => 100], /* ... */ - Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + 'routing' => [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Helper\UrlHelperMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], /* ... */ ], ]; diff --git a/doc/book/helpers/url-helper.md b/doc/book/helpers/url-helper.md index 52f979a3..9373f3ba 100644 --- a/doc/book/helpers/url-helper.md +++ b/doc/book/helpers/url-helper.md @@ -140,7 +140,7 @@ $app->pipeDispatchMiddleware(); // [ // 'middleware_pipeline' => [ // /* ... */ -// [ +// 'routing' => [ // 'middleware' => [ // Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, // UrlHelperMiddleware::class @@ -181,7 +181,7 @@ return [ ], ], 'middleware_pipeline' => [ - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, UrlHelperMiddleware::class, diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index 1aa1c045..8a60a7aa 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -298,20 +298,20 @@ This would be rewritten to the following to work with RC6 and later: ```php return [ 'middleware_pipeline' => [ - [ + 'always' => [ 'middleware' => [ Zend\Expressive\Helper\ServerUrlMiddleware::class, DebugToolbarMiddleware::class, ], 'priority' => PHP_INT_MAX, ], - [ + 'api' => [ 'middleware' => ApiMiddleware::class, 'path' => '/api, 'priority' => 100, ], - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, Zend\Expressive\Helper\UrlHelperMiddleware::class, @@ -320,7 +320,7 @@ return [ 'priority' => 1, ], - [ + 'error' => [ 'middleware' => [ NotFoundMiddleware::class, ], @@ -336,6 +336,16 @@ priority, you can simplify adding new middleware, particularly if you know it should execute before routing, or as error middleware, or between routing and dispatch. +> #### Keys are ignored +> +> The above example provides keys for each middleware specification. The factory +> will ignore these, but they can be useful for cases when you might want to +> specify configuration in multiple files, and merge specific entries together. +> Be aware, however, that the `middleware` key itself is an indexed array; +> items will be appended based on the order in which configuration files are +> merged. If order of these is important, create separate specifications with +> relevant `priority` values. + ## Route result observer deprecation As of RC6, the following changes have occurred with regards to route result @@ -378,7 +388,7 @@ however, you will need to register it following the routing middleware: [ 'middleware_pipeline' => [ /* ... */ - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, Zend\Expressive\Container\ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE, @@ -460,7 +470,7 @@ If you are using the `ApplicationFactory`, alter your configuration: [ 'middleware_pipeline' => [ /* ... */ - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, ['middleware' => MyObserver::class], diff --git a/doc/book/usage-examples.md b/doc/book/usage-examples.md index b5eadbaf..c033534e 100644 --- a/doc/book/usage-examples.md +++ b/doc/book/usage-examples.md @@ -538,7 +538,7 @@ constants in such arrays as well: return [ 'middleware_pipeline' => [ [ /* ... */ ], - [ + 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, /* ... middleware that introspects routing results ... */ @@ -551,6 +551,16 @@ return [ ]; ``` +> #### Pipeline keys are ignored +> +> Keys in a `middleware_pipeline` specification are ignored. However, they can +> be useful when merging several configurations; if multiple configuration files +> specify the same key, then those entries will be merged. Be aware, however, +> that the `middleware` entry for each, since it is an indexed array, will +> merge arrays by appending; in other words, order will not be guaranteed within +> that array after merging. If order is critical, define a middleware spec with +> `priority` keys. + The path, if specified, can only be a literal path to match, and is typically used for segregating middleware applications or applying rules to subsets of an application that match a common path root. From 07f6a03bb39457aede1ca7e387f8d79758a2b3c5 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 18 Jan 2016 10:58:05 -0600 Subject: [PATCH 15/18] Remove nested middleware array Thanks, @harikt! --- doc/book/cookbook/setting-locale-depending-routing-parameter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/book/cookbook/setting-locale-depending-routing-parameter.md b/doc/book/cookbook/setting-locale-depending-routing-parameter.md index 50d3b6a9..974c78e3 100644 --- a/doc/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/doc/book/cookbook/setting-locale-depending-routing-parameter.md @@ -143,7 +143,7 @@ return [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, Helper\UrlHelperMiddleware::class, - [ 'middleware' => LocalizationMiddleware::class ], + LocalizationMiddleware::class, Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], 'priority' => 1, From 9a00c36dca6f4e497ab0d53a80b14a3456fe2697 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 18 Jan 2016 11:08:31 -0600 Subject: [PATCH 16/18] Do not nest middleware specs Found one more place where a middleware spec was nested inside another. --- doc/book/migration/rc-to-v1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/book/migration/rc-to-v1.md b/doc/book/migration/rc-to-v1.md index 8a60a7aa..dcc7a416 100644 --- a/doc/book/migration/rc-to-v1.md +++ b/doc/book/migration/rc-to-v1.md @@ -473,7 +473,7 @@ If you are using the `ApplicationFactory`, alter your configuration: 'routing' => [ 'middleware' => [ Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, - ['middleware' => MyObserver::class], + MyObserver::class, Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, ], 'priority' => 1, From 42f6fc586cd5d6747d3a223911be1cb6fb47b647 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 18 Jan 2016 12:11:53 -0600 Subject: [PATCH 17/18] Incorporated feedback for flow diagram Changes suggested by @ZergMark --- doc/book/images/architecture.png | Bin 41902 -> 42135 bytes doc/book/images/architecture.xcf | Bin 148938 -> 198547 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/book/images/architecture.png b/doc/book/images/architecture.png index 1425367a8dc8952b4e12cd6846c7df7cd1b20dec..c3a99bdaf15d20f408982ba0159b2373f82aea84 100644 GIT binary patch literal 42135 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 literal 41902 zcmd43bzD^4yEi<9AW8@*9g2iXgLJ91w1{+f4oD0z3?V8YB`RGa-5}B}C9QOKNDa-< z^Q`f{f9H3f^PKa%@p=DvQFOCs@4fcE*0t99# z#8o`7$928q6Zj9$Oi5k_g2w#$T%Y|B>>+Sg&~*i8jAFjAp4?0H1UvEF6jfyL=dj2~ z#IERB=o3O9w;_r$kDq%^Y)*UmJRiP5?~e9Z@F^->)%EANadh=Dg{4NQ{}&zoKr>F8 z%+Tqd6y>MxSdP&v_MzV{F25yCFxiz4GYwJL=T+G7-sa-yYQTF!cKgv21wP#$%<(4| zydUp$Gh2L6=IcH?@mz{p>Pd$8OTgm$B~Y&Y{=xtM{lX>0`Ki`>n`yxO0~Y&E@(pMZ z8iyEXeuCvyKfZr@r?^<#n?q_4Jk35BovsBLk5aZNdhldA5k&jQC{J-W5?-XmmA z;$(_oYJTEJqJic1YZJHuQV+GAJ1y(h_mQFYry~UULK9MgeGiMn?%kaDiHy*Fer$ci zWzCz;e~uw%)FU3kx){C*QFh%5z3x+eBYk3=do19vs8Rl#CnudJ?%4Gt990}{EJcQu zkJxp@pKvUj7z4O#JTmMPm0|VyoB5nGXbJ6;Xu}+7@g3T^d+OIeVT&}mb7oC$>y302 zp;NO@9D`aRcBqJ=eQm=HK^#x&9B0RDsY;gxB{#8s3*G&pM! zH%1WmXpj3PD5HWm%1MP?MU1coA9_vG+}=X=iVB_bt6vpIP8h*zRBy!2 zZ>~V6Qnqmx{V5Ihldp(hGg!K#gLhoNfqTi!LsQi~TvVp7Jo@X@dy<$*^ynNtrsSRs zn_XXV^-Hfc4CoFmaun+uc5*YD=ySLZ-SB5um|Va1BfRK3HUTzcl_1i2RO`FVoat!u zB~~BX5e*`7znVnFaQ;DKrQ$fuz%o?dAenB>j3idbJ=m?VLy2Lewh+E3flw-~ot#c| zcdF}}QQ2oRi>c{??v_X}LU|)c4VPo#V=4MJGxish+FnoCHa1lL4lK07odjiQA5-zJh&YgYEP-L3^5; zLNjKb4Q+7kZ=UtJjTKdVlMAO3IUf%Xv*=~qHm|goj(T1%|3=$?!8y4m6Z$nlRdq7n zao&-hCfVhJVJ;u%QRPi|e{WHs!_B@)``LD{v*NV?p=-Yb%dSs-%n}1VZz4HtmT=BB zGMFg(5Q4&KbWPTga!vM}`5r%bBW!CX-d%EH0c~FN@oGKd@{7gz~-;s`uP>7E6tz%xGre_5+zm?=tRE5 zt~aaK-4RbRr>HA5np?Q5Q1fq+>Kfn zJ!r+7N_U$zL{haeo-zH^uK7L2Mz=#Ai3z6QcUfLcKIsYxY$yTA`MpAl!KwB6`YT1V z+7IvZX!8Ex2_X374r& zI8A5>dqCO4?e+3GI@b(_Rbg%vR%#7#QH+Ga9348(Wdh#-4Lx+$T2Y~YyRd2HJ6wLw z=zMAW!Fs|bl4mYs0})xP>JR&Qarg2xZ@ON0wLO1Rvni-(Ew$N8%#6Pmx~At(FTAgm z(&05Z≻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 diff --git a/doc/book/images/architecture.xcf b/doc/book/images/architecture.xcf index 0a0a4925407d5a6cb23b3e350d41597576936e13..9386175816c842bd28ee463bb370770e12e47e6e 100644 GIT binary patch literal 198547 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 delta 14181 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 Date: Mon, 18 Jan 2016 14:33:45 -0600 Subject: [PATCH 18/18] Added CHANGELOG for #270 --- CHANGELOG.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7318cca8..275610b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,32 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. -## 1.0.0 - TBD - -First stable release. +## 1.0.0rc6 - TBD + +Sixth release candidate. + +This release contains backwards compatibility breaks with previous release +candidates. All previous functionality should continue to work, but will +emit `E_USER_DEPRECATED` notices prompting you to update your application. +In particular: + +- The routing middleware has been split into two separate middleware + implementations, one for routing, another for dispatching. This eliminates the + need for the route result observer system, as middleware can now be placed + *between* routing and dispatching — an approach that provides for greater + flexibility with regards to providing route-based functionality. +- As a result of the above, `Zend\Expressive\Application` no longer implements + `Zend\Expressive\Router\RouteResultSubjectInterface`, though it retains the + methods associated (each emits a deprecation notice). +- Configuration for `Zend\Expressive\Container\ApplicationFactory` was modified + to implement the `middleware_pipeline` as a single queue, instead of + segregating it between `pre_routing` and `post_routing`. Each item in the + queue follows the original middleware specification from those keys, with one + addition: a `priority` key can be used to allow you to granularly shape the + execution order of the middleware pipeline. + +A [migration guide](http://zend-expressive.rtfd.org/en/latest/migration/rc-to-v1/) +was written to help developers migrate to RC6 from earlier versions. ### Added @@ -24,14 +47,44 @@ First stable release. a flow/architectural diagram to the "features" chapter. - [#262](https://github.com/zendframework/zend-expressive/pull/262) adds a recipe demonstrating creating classes that can intercept multiple routes. +- [#270](https://github.com/zendframework/zend-expressive/pull/270) adds + new methods to `Zend\Expressive\Application`: + - `dispatchMiddleware()` is new middleware for dispatching the middleware + matched by routing (this functionality was split from `routeMiddleware()`). + - `routeResultObserverMiddleware()` is new middleware for notifying route + result observers, and exists only to aid migration functionality; it is + marked deprecated! + - `pipeDispatchMiddleware()` will pipe the dispatch middleware to the + `Application` instance. + - `pipeRouteResultObserverMiddleware()` will pipe the route result observer + middleware to the `Application` instance; like + `routeResultObserverMiddleware()`, the method only exists for aiding + migration, and is marked deprecated. +- [#270](https://github.com/zendframework/zend-expressive/pull/270) adds + `Zend\Expressive\MarshalMiddlewareTrait`, which is composed by + `Zend\Expressive\Application`; it provides methods for marshaling + middleware based on service names or arrays of services. ### Deprecated -- Nothing. +- [#270](https://github.com/zendframework/zend-expressive/pull/270) deprecates + the following methods in `Zend\Expressive\Application`, all of which will + be removed in version 1.1: + - `attachRouteResultObserver()` + - `detachRouteResultObserver()` + - `notifyRouteResultObservers()` + - `pipeRouteResultObserverMiddleware()` + - `routeResultObserverMiddleware()` ### Removed -- Nothing. +- [#270](https://github.com/zendframework/zend-expressive/pull/270) removes the + `Zend\Expressive\Router\RouteResultSubjectInterface` implementation from + `Zend\Expressive\Application`. +- [#270](https://github.com/zendframework/zend-expressive/pull/270) eliminates + the `pre_routing`/`post_routing` terminology from the `middleware_pipeline`, + in favor of individually specified `priority` values in middleware + specifications. ### Fixed