diff --git a/.docheader b/.docheader index 5f13b6d4..9bba16ae 100644 --- a/.docheader +++ b/.docheader @@ -1,5 +1,7 @@ /** * @see https://github.com/zendframework/zend-expressive for the canonical source repository - * @copyright Copyright (c) %regexp:(20\d{2}-)?20\d{2}% Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) %regexp:(20\d{2}-)?20\d{2}% Zend Technologies USA Inc. (https://www.zend.com) * @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License */ + +declare(strict_types=1); diff --git a/.travis.yml b/.travis.yml index 3e8ccc45..02802796 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,28 +13,6 @@ env: matrix: include: - - php: 5.6 - env: - - DEPS=lowest - - LEGACY_DEPS="phpunit/phpunit malukenho/docheader" - - php: 5.6 - env: - - DEPS=locked - - LEGACY_DEPS="phpunit/phpunit malukenho/docheader" - - php: 5.6 - env: - - DEPS=latest - - LEGACY_DEPS="phpunit/phpunit malukenho/docheader" - - php: 7 - env: - - DEPS=lowest - - php: 7 - env: - - DEPS=locked - - LEGACY_DEPS="phpunit/phpunit symfony/console symfony/debug symfony/finder" - - php: 7 - env: - - DEPS=latest - php: 7.1 env: - DEPS=lowest @@ -52,6 +30,7 @@ matrix: - php: 7.2 env: - DEPS=locked + - PHPSTAN_CHECK=true - php: 7.2 env: - DEPS=latest @@ -60,8 +39,7 @@ before_install: - if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi install: - - travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs - - if [[ $LEGACY_DEPS != '' ]]; then travis_retry composer update $COMPOSER_ARGS --with-dependencies $LEGACY_DEPS ; fi + - travis_retry composer install $COMPOSER_ARGS - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS $COVERAGE_DEPS ; fi @@ -73,6 +51,7 @@ script: - if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi - if [[ $CS_CHECK == 'true' ]]; then composer cs-check ; fi - if [[ $CS_CHECK == 'true' ]]; then composer license-check ; fi + - if [[ $PHPSTAN_CHECK == 'true' ]]; then composer phpstan ; fi after_script: - if [[ $TEST_COVERAGE == 'true' ]]; then vendor/bin/php-coveralls -v ; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 30891f5e..87b7001c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,658 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 3.0.0rc3 - 2018-03-07 + +### Added + +- Nothing. + +### Changed + +- [#579](https://github.com/zendframework/zend-expressive/pull/579) updates the + version constraint for zend-expressive-router to use 3.0.0rc4 or later. + +- [#579](https://github.com/zendframework/zend-expressive/pull/579) updates the + version constraint for zend-stratigility to use 3.0.0rc1 or later. + +### Deprecated + +- Nothing. + +### Removed + +- [#580](https://github.com/zendframework/zend-expressive/pull/580) removes + zend-diactoros as a requirement; all usages of it within the package are + currently conditional on it being installed, and can be replaced easily with + any other PSR-7 implementation at this time. + +### Fixed + +- Nothing. + +## 3.0.0rc2 - 2018-03-06 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#578](https://github.com/zendframework/zend-expressive/pull/578) fixes the + version constraint used with zend-stratigility to allow it to update to later + versions when released. + +## 3.0.0rc1 - 2018-02-27 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#574](https://github.com/zendframework/zend-expressive/pull/574) updates the + classes `Zend\Expressive\Exception\InvalidMiddlewareException` and + `MissingDependencyException` to implement the + [PSR-11](https://www.php-fig.org/psr/psr-11/) `ContainerExceptionInterface`. + +## 3.0.0alpha9 - 2018-02-22 + +### Added + +- [#562](https://github.com/zendframework/zend-expressive/pull/562) adds the + class `Zend\Expressive\Response\ServerRequestErrorResponseGenerator`, and maps + it to the `Zend\Expressive\Container\ServerRequestErrorResponseGeneratorFactory`. + The class generates an error response when an exeption occurs producing a + server request instance, and can be optionally templated. + +### Changed + +- [#568](https://github.com/zendframework/zend-expressive/pull/568) updates the + zendframework/zend-stratigility dependency to require at least 3.0.0alpha4. + +- [#568](https://github.com/zendframework/zend-expressive/pull/568) updates the + `ErrorHandlerFactory` to pull the `Psr\Http\Message\ResponseInterface` + service, which returns a factory capable of returning a response instance, + and passes it to the `Zend\Stratigility\Middleware\ErrorHandler` instance it + creates, as that class changes in 3.0.0alpha4 such that it now expects a + factory instead of an instance. + +- [#562](https://github.com/zendframework/zend-expressive/pull/562) modifies the + `Zend\Expressive\Container\RequestHandlerRunnerFactory` to depend on the + `Zend\Expressive\Response\ServerRequestErrorResponseGenerator` service instead + of the `Zend\Expressive\SERVER_REQUEST_ERROR_RESPONSE_GENERATOR` virtual + service. + +- [#562](https://github.com/zendframework/zend-expressive/pull/562) extracts + most logic from `Zend\Expressive\Middleware\ErrorResponseGenerator` to a new + trait, `Zend\Expressive\Response\ErrorResponseGeneratorTrait`. A trait was + used as the classes consuming it are from different namespaces, and thus + different inheritance trees. The trait is used by both the + `ErrorResponseGenerator` and the new `ServerRequestErrorResponseGenerator`. + +### Deprecated + +- Nothing. + +### Removed + +- [#562](https://github.com/zendframework/zend-expressive/pull/562) removes the + constant `Zend\Expressive\SERVER_REQUEST_ERROR_RESPONSE_GENERATOR`. It was + only used internally previously. + +### Fixed + +- Nothing. + +## 3.0.0alpha8 - 2018-02-21 + +### Added + +- Nothing. + +### Changed + +- [#559](https://github.com/zendframework/zend-expressive/pull/559) reverts the + changes performed for [#556](https://github.com/zendframework/zend-expressive/pull/556) + to the `ApplicationFactory`. It now uses the canonical service name for the + `PathBasedRoutingMiddleware` instead of the `ROUTE_MIDDLEWARE` constant. + +- [#561](https://github.com/zendframework/zend-expressive/pull/561) updates to + zend-expressive-router 3.0.0alpha3. + +- [#561](https://github.com/zendframework/zend-expressive/pull/561) renames + `Zend\Expressive\Container\ResponseFactory` to `Zend\Expressive\Container\ResponseFactoryFactory`, + and the factory now returns a callable that will return a zend-diactoros + `Response` instance, instead of returning the instance itself. Each of the + various services named after zend-expressive-router response constants were + removed in favor of a single `Psr\Http\Message\ResponseInterface` service + resolving to the `ResponseFactoryFactory`. + +- [#561](https://github.com/zendframework/zend-expressive/pull/561) modifies the + `Zend\Expressive\Handler\NotFoundHandler` to compose a response factory + instead of a response prototype. This approach allows it to use the + `Psr\Http\Message\ResponseInterface` service defined per the above note. + +- [#561](https://github.com/zendframework/zend-expressive/pull/561) renames + the `Zend\Expressive\Router\IMPLICIT_HEAD_MIDDLEWARE_STREAM_FACTORY` service + to `Psr\Http\Message\StreamInterface`, as this is what zend-expressive-router + now expects. + +- [#561](https://github.com/zendframework/zend-expressive/pull/561) renames the + `Zend\Expressive\ServerRequestFactory` service to + `Psr\Http\Message\ServerRequestInterface`. The + `Zend\Expressive\SERVER_REQUEST_FACTORY` constant now resolves to the + interface name. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#558](https://github.com/zendframework/zend-expressive/pull/558) adds a + missing import statement for the PSR-7 `StreamInterface` to the + `StreamFactoryFactory`. + +- [#555](https://github.com/zendframework/zend-expressive/pull/555) adds tests + to better ensure that the entries in the `ConfigProvider` resolve to valid + factories, aliases, etc. + +## 3.0.0alpha7 - 2018-02-14 + +### Added + +- Nothing. + +### Changed + +- [#556](https://github.com/zendframework/zend-expressive/pull/556) modifies the + `ApplicationFactory` such that it now uses the + `Zend\Expressive\ROUTE_MIDDLEWARE` constant in order to retrieve the + `Zend\Expressive\Router\Middleware\PathBasedRoutingMiddleware` instance. + This is done to help smooth upgrades from v2 to v3, as it prevents a manual + step when updating the `config/pipeline.php`, and ensures that the instance + composed in the application is the same instance piped to the application. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 3.0.0alpha6 - 2018-02-14 + +### Added + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) and + [#553](https://github.com/zendframework/zend-expressive/pull/553) add + `Zend\Expressive\Container\StreamFactoryFactory`, for producing an callable + capable of producing an empty, writable PSR-7 `StreamInterface` instance using + zend-diactoros. The stream produced is backed by a `php://temp` stream. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) and + [#554](https://github.com/zendframework/zend-expressive/pull/554) add + the following constants under the `Zend\Expressive` namespace: + + - `DEFAULT_DELEGATE` can be used to refer to the former `DefaultDelegate` FQCN service. + - `IMPLICIT_HEAD_MIDDLEWARE` can be used to refer to the former `Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware` service. + - `IMPLICIT_OPTIONS_MIDDLEWARE` can be used to refer to the former `Zend\Expressive\Router\Middleware\ImplicitOPTIONSMiddleware` service. + - `NOT_FOUND_MIDDLEWARE` can be used to refer to the former `Zend\Expressive\Middleware\NotFoundMiddleware` service. + - `NOT_FOUND_RESPONSE` can be used to refer to the former `Zend\Expressive\Response\NotFoundResponseInterface` service. + - `SERVER_REQUEST_ERROR_RESPONSE_GENERATOR` can be used to refer to the former `Zend\Expressive\ServerRequestErrorResponseGenerator` service. + - `SERVER_REQUEST_FACTORY` can be used to refer to the former `Zend\Expressive\ServerRequestFactory` service. + +### Changed + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) updates + dependencies to pin to zend-expressive-router 3.0.0alpha2 or later. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) renames + `Zend\Expressive\Middleware\NotFoundMiddleware` to + `Zend\Expressive\Handler\NotFoundHandler`, which allows it to be used as a + PSR-15 request handler, and, when piped or routed to, also as middleware. + The original class name was aliased to the renamed class in the + `ConfigProvider`. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) modifies the + `ApplicationConfigInjectionDelegator` to raise an exception if the callback + passed to it does not produce an `Application` instance, instead of returning + the instance without changes. This allows developers to understand what they + need to correct in their service configuration. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) updates + the `ConfigProvider` to add entries for the following zend-expressive-router + constants as follows: + + - `IMPLICIT_HEAD_MIDDLEWARE_RESPONSE` maps to the `ResponseFactory`. + - `IMPLICIT_HEAD_MIDDLEWARE_STREAM_FACTORY` maps to the `StreamFactory`. + - `IMPLICIT_OPTIONS_MIDDLEWARE_RESPONSE` maps to the `ResponseFactory`. + +- [#554](https://github.com/zendframework/zend-expressive/pull/554) updates + the `ConfigProvider` to add entries for the following constants as follows: + + - `IMPLICIT_HEAD_MIDDLEWARE` aliases to the `Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware` service. + - `IMPLICIT_OPTIONS_MIDDLEWARE` aliases to the `Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware` service. + +### Deprecated + +- Nothing. + +### Removed + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) removes + `Zend\Expressive\Container\RouteMiddlewareFactory`, as zend-expressive-router + now provides a factory for the middleware. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) removes + `Zend\Expressive\Container\DispatchMiddlewareFactory`, as zend-expressive-router + now provides a factory for the middleware. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) removes + `Zend\Expressive\Middleware\ImplicitHeadMiddleware`, as it is now provided by + the zend-expressive-router package. + +- [#551](https://github.com/zendframework/zend-expressive/pull/551) removes + `Zend\Expressive\Middleware\ImplicitOptionsMiddleware`, as it is now provided + by the zend-expressive-router package. + +### Fixed + +- Nothing. + +## 3.0.0alpha5 - 2018-02-07 + +### Added + +- Nothing. + +### Changed + +- [#547](https://github.com/zendframework/zend-expressive/pull/547) modifies the + `ConfigProvider`, the `NotFoundMiddlewareFactory`, and the + `RouteMiddlewareFactory` to remove the concept of the _unshared_ + `ResponseInterface` service, as service sharing is not always configurable in + container implementations. To resolve the ability to provide discrete + instances, the `ConfigProvider` defines two new virtual services that each + resolve to the `Zend\Expressive\Container\ResponseFactory`: + + - `Zend\Expressive\Response\NotFoundResponseInterface` + - `Zend\Expressive\Response\RouterResponseInterface` + + The related factories now consume these services in order to receive a + response prototype for the services they produce. + +- [#542](https://github.com/zendframework/zend-expressive/pull/542) modifies the + `composer.json` to no longer suggest the pimple/pimple package, but rather the + zendframework/zend-pimple-config package. + +- [#542](https://github.com/zendframework/zend-expressive/pull/542) modifies the + `composer.json` to no longer suggest the aura/di package, but rather the + zendframework/zend-auradi-config package. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 3.0.0alpha4 - 2018-02-07 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#549](https://github.com/zendframework/zend-expressive/pull/549) modifies how + the `ServerRequestFactoryFactory` returns the + `ServerRequestFactory::fromGlobals()` mechanism, wrapping it in an anonymous + function. This ensures compatibility across all containers. + +- [#550](https://github.com/zendframework/zend-expressive/pull/550) fixes how + the `ConfigProvider` references the `ErrorResponseGenerator`, using the + `Zend\Expressive\Middleware` namespace instead of the + `Zend\Stratigility\Middleware` namespace. + +## 3.0.0alpha3 - 2018-02-06 + +### Added + +- Nothing. + +### Changed + +- [#546](https://github.com/zendframework/zend-expressive/pull/546) merges + `Zend\Expressive\Middleware\NotFoundHandler` into + `Zend\Expressive\Middleware\NotFoundMiddleware`, as well as merges + `Zend\Expressive\Container\NotFoundHandlerFactory` into + `Zend\Expressive\Container\NotFoundMiddlewareFactory`. `NotFoundMiddleware` + now does the work of the former `Zend\Expressive\Delegate\NotFoundDelegate`, + and, as such, accepts the following constructor arguments: + + - PSR-7 `ResponseInterface $responsePrototype` (required) + - `Zend\Expressive\Template\TemplateRendererInterface $renderer` (optional) + - `string $template = self::TEMPLATE_DEFAULT` (optional; defaults to "error::404") + - `string $layout = self::LAYOUT_DEFAULT` (optional; defaults to "layout::default") + +### Deprecated + +- Nothing. + +### Removed + +- [#546](https://github.com/zendframework/zend-expressive/pull/546) removes the + class `Zend\Expressive\Delegate\DefaultDelegate`, as there is no longer a + concept of a default handler invoked by the application. Instead, developers + MUST pipe middleware at the innermost layer of the pipeline guaranteed to + return a response; we recommend using `Zend\Expressive\Middleware\NotFoundMiddleware` + for this purpose. + +### Fixed + +- Nothing. + +## 3.0.0alpha2 - 2018-02-05 + +### Added + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds support + for the final PSR-15 interfaces, and explicitly depends on + psr/http-server-middleware. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds a new + class, `Zend\Expressive\MiddlewareContainer`. The class decorates a PSR-11 + `ContainerInterface`, and adds the following behavior: + + - If a class is not in the container, but exists, `has()` will return `true`. + - If a class is not in the container, but exists, `get()` will attempt to + instantiate it, caching the instance locally if it is valid. + - Any instance pulled from the container or directly instantiated is tested. + If it is a PSR-15 `RequestHandlerInterface`, it will decorate it in a + zend-stratigility `RequestHandlerMiddleware` instance. If the instance is + not a PSR-15 `MiddlewareInterface`, the container will raise a + `Zend\Expressive\Exception\InvalidMiddlewareException`. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds a new + class, `Zend\Expressive\MiddlewareFactory`. The class composes a + `MiddlewareContainer`, and exposes the following methods: + + - `callable(callable $middleware) : CallableMiddlewareDecorator` + - `handler(RequestHandlerInterface $handler) : RequestHandlerMiddleware` + - `lazy(string $service) : LazyLoadingMiddleware` + - `prepare($middleware) : MiddlewareInterface`: accepts a string service name, + callable, `RequestHandlerInterface`, `MiddlewareInterface`, or array of such + values, and returns a `MiddlewareInterface`, raising an exception if it + cannot determine what to do. + - `pipeline(...$middleware) : MiddlewarePipe`: passes each argument to + `prepare()`, and the result to `MiddlewarePipe::pipe()`, returning the + pipeline when complete. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds + the following factory classes, each within the `Zend\Expressive\Container` + namespace: + + - `ApplicationPipelineFactory`: creates and returns a + `Zend\Stratigility\MiddlewarePipe` to use as the application middleware + pipeline. + - `DispatchMiddlewareFactory`: creates and returns a `Zend\Expressive\Router\DispatchMiddleware` instance. + - `EmitterFactory`: creates and returns a + `Zend\HttpHandlerRunner\Emitter\EmitterStack` instance composing an + `SapiEmitter` from that same namespace as the only emitter on the stack. + This is used as a dependency for the `Zend\HttpHandlerRunner\RequestHandlerRunner` + service. + - `MiddlewareContainerFactory`: creates and returns a `Zend\Expressive\MiddlewareContainer` + instance decorating the PSR-11 container passed to the factory. + - `MiddlewareFactoryFactory`: creates and returns a `Zend\Expressive\MiddlewareFactory` + instance decorating a `MiddlewareContainer` instance as pulled from the + container. + - `RequestHandlerRunnerFactory`: creates and returns a + `Zend\HttpHandlerRunner\RequestHandlerRunner` instance, using the services + `Zend\Expressive\Application`, `Zend\HttpHandlerRunner\Emitter\EmitterInterface`, + `Zend\Expressive\ServerRequestFactory`, and `Zend\Expressive\ServerRequestErrorResponseGenerator`. + - `RouteMiddlewareFactory`: creates and returns a `Zend\Expressive\Router\PathBasedRoutingMiddleware` instance. + - `ServerRequestFactoryFactory`: creates and returns a `callable` factory for + generating a PSR-7 `ServerRequestInterface` instance; this returned factory is a + dependency for the `Zend\HttpHandlerRunner\RequestHandlerRunner` service. + - `ServerRequestErrorResponseGeneratorFactory`: creates and returns a + `callable` that accepts a PHP `Throwable` in order to generate a PSR-7 + `ResponseInterface` instance; this returned factory is a dependency for the + `Zend\HttpHandlerRunner\RequestHandlerRunner` service, which uses it to + generate a response in the scenario that the `ServerRequestFactory` is + unable to create a request instance. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds + the class `Zend\Expressive\Container\ApplicationConfigInjectionDelegator`. + This class may be used either as a delegator factory on the + `Zend\Expressive\Application` instance, or you may use the two static methods + it defines to inject pipeline middleware and/or routes from configuration: + + - `injectPipelineFromConfig(Application $application, array $config) : void` + - `injectRoutesFromConfig(Application $application, array $config) : void` + + These methods work the same way as the associated `Application` methods from + version 2, accepting the same configuration. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds + `Zend\Expressive\ConfigProvider`, which details the default service mappings. + +### Changed + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) adds + dependencies on each of: + + - zend-stratigility 3.0.0alpha3 + - zend-expressive-router 3.0.0alpha1 + - zend-httphandlerrunner 1.0.0 + + and removes the dependency http-interop/http-server-middleware. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) renames + `Zend\Expressive\Middleware\NotFoundHandler` to + `Zend\Expressive\Middleware\NotFoundMiddleware`, and its accompanying factory + `Zend\Expressive\Container\NotFoundHandlerFactory` to + `Zend\Expressive\Container\NotFoundMiddlewareFactory`. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) renames + `Zend\Expressive\Delegate\NotFoundDelegate` to + `Zend\Expressive\Handler\NotFoundHandler`, updating it to implement the PSR-15 + `RequestHandlerInterface`. It also renames the factory + `Zend\Expressive\Container\NotFoundDelegateFactory` to + `Zend\Expressive\Container\NotFoundHandlerFactory`. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) refactors + `Zend\Expressive\Application` completely. + + The class no longer extends `Zend\Stratigility\MiddlewarePipe`, and instead + implements the PSR-15 `MiddlewareInterface` and `RequestHandlerInterface`. + + It now **requires** the following dependencies via constructor injection, in + the following order: + + - `Zend\Expressive\MiddlewareFactory` + - `Zend\Stratigility\MiddlewarePipe`; this is the pipeline representing the application. + - `Zend\Expressive\Router\PathBasedRoutingMiddleware` + - `Zend\HttpHandlerRunner\RequestHandlerRunner` + + It removes all "getter" methods (as detailed in the "Removed" section of this + release), but retains the following methods, with the changes described below. + Please note: in most cases, these methods accept the same arguments as in the + version 2 series, with the exception of callable double-pass middleware (these + may be decorated manually using `Zend\Stratigility\doublePassMiddleware()`), + and http-interop middleware (no longer supported; rewrite as PSR-15 + middleware). + + - `pipe($middlewareOrPath, $middleware = null) : void` passes its arguments to + the composed `MiddlewareFactory`'s `prepare()` method; if two arguments are + provided, the second is passed to the factory, and the two together are + passed to `Zend\Stratigility\path()` in order to decorate them to work as + middleware. The prepared middleware is then piped to the composed + `MiddlewarePipe` instance. + + As a result of switching to use the `MiddlewareFactory` to prepare + middleware, you may now pipe `RequestHandlerInterface` instances as well. + + - `route(string $path, $middleware, array $methods = null, string $name) : Route` + passes its `$middleware` argument to the `MiddlewareFactory::prepare()` + method, and then all arguments to the composed `PathBasedRoutingMiddleware` + instance's `route()` method. + + As a result of switching to use the `MiddlewareFactory` to prepare + middleware, you may now route to `RequestHandlerInterface` instances as + well. + + - Each of `get`, `post`, `patch`, `put`, `delete`, and `any` now proxy to + `route()` after marshaling the correct `$methods`. + + - `getRoutes() : Route[]` proxies to the composed `PathBasedRoutingMiddleware` + instance. + + - `handle(ServerRequestInterface $request) : ResponseInterface` proxies to the + composed `MiddlewarePipe` instance's `handle()` method. + + - `process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface` + proxies to the composed `MiddlewarePipe` instance's `process()` method. + + - `run() : void` proxies to the composed `RequestHandlerRunner` instance. + Please note that the method no longer accepts any arguments. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) modifies the + `Zend\Expressive\Container\ApplicationFactory` to reflect the changes to the + `Zend\Expressive\Application` class as detailed above. It pulls the following + services to inject via the constructor: + + - `Zend\Expressive\MiddlewareFactory` + - `Zend\Stratigility\ApplicationPipeline`, which should resolve to a + `MiddlewarePipe` instance; use the + `Zend\Expressive\Container\ApplicationPipelineFactory`. + - `Zend\Expressive\Router\PathBasedRoutingMiddleware` + - `Zend\HttpHandlerRunner\RequestHandlerRunner` + +### Deprecated + +- Nothing. + +### Removed + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes + support for http-interop/http-server-middleware. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes the + class `Zend\Expressive\Middleware\RouteMiddleware`. Use the + `PathBasedRoutingMiddleware` or `RouteMiddleware` from zend-expressive-router + instead; the factory `Zend\Expressive\Container\RouteMiddlewareFactory` will + return a `PathBasedRoutingMiddleware` instance for you. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes the + class `Zend\Expressive\Middleware\DispatchMiddleware`. Use the + `DispatchMiddleware` from zend-expressive-router instead; the factory + `Zend\Expressive\Container\DispatchMiddlewareFactory` will return an instance + for you. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes the + class `Zend\Expressive\Emitter\EmitterStack`; use the class + `Zend\HttpHandlerRunner\Emitter\EmitterStack` instead. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes the + following methods from `Zend\Expressive\Application`: + + - `pipeRoutingMiddleware()`: use `pipe(\Zend\Expressive\Router\PathBasedRoutingMiddleware::class)` instead. + - `pipeDispatchMiddleware()`: use `pipe(\Zend\Expressive\Router\DispatchMiddleware::class)` instead. + - `getContainer()` + - `getDefaultDelegate()`: ensure you pipe middleware capable of returning a response at the innermost layer; this can be done by decorating a request handler using `Zend\Stratigility\Middleware\RequestHandlerMiddleware`, using `Zend\Expressive\Middleware\NotFoundMiddleware`, or other approaches. + - `getEmitter()`: use the `Zend\HttpHandlerRunner\Emitter\EmitterInterface` service from the container. + - `injectPipelineFromConfig()`: use the new `ApplicationConfigInjectionDelegator` and/or the static method of the same name it defines. + - `injectRoutesFromConfig()`: use the new `ApplicationConfigInjectionDelegator` and/or the static method of the same name it defines. + +- [#543](https://github.com/zendframework/zend-expressive/pull/543) removes the + class `Zend\Expressive\AppFactory`. + +### Fixed + +- Nothing. + +## 3.0.0alpha1 - 2018-01-22 + +### Added + +- [#529](https://github.com/zendframework/zend-expressive/pull/529) adds support + for http-interop/http-server-middleware (PSR-15 pre-cursor). + +- [#538](https://github.com/zendframework/zend-expressive/pull/538) adds scalar + and return type hints to methods wherever possible. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [#529](https://github.com/zendframework/zend-expressive/pull/529) removes + support for PHP versions prior to PHP 7.1. + +- [#529](https://github.com/zendframework/zend-expressive/pull/529) removes + support for http-interop/http-middleware (previous PSR-15 iteration). + +### Fixed + +- Nothing. + ## 2.2.0 - 2018-03-12 ### Added diff --git a/README.md b/README.md index bce397a4..abf31ef6 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,10 @@ can recommend the following implementations: - [zend-servicemanager](https://github.com/zendframework/zend-servicemanager): `composer require zendframework/zend-servicemanager` -- [pimple-container-interop](https://github.com/xtreamwayz/pimple-container-interop): - `composer require xtreamwayz/pimple-container-interop` -- [Aura.Di](https://github.com/auraphp/Aura.Di): - `composer require aura/di` +- [Pimple](https://github.com/silexphp/Pimple) (see [docs](docs/book/features/container/pimple.md) for more details): + `composer require zendframework/zend-pimple-config` +- [Aura.Di](https://github.com/auraphp/Aura.Di) (see [docs](docs/book/features/container/aura-di.md) for more details): + `composer require zendframework/zend-auradi-config` Additionally, you may optionally want to install a template renderer implementation, and/or an error handling integration. These are covered in the diff --git a/bin/expressive-tooling b/bin/expressive-tooling index 7806d796..80f6fbbd 100755 --- a/bin/expressive-tooling +++ b/bin/expressive-tooling @@ -4,10 +4,12 @@ * Script for migrating configuration-driven pipelines/routes to programmatic usage. * * @see https://github.com/zendframework/zend-expressive for the canonical source repository - * @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2016-2017 Zend Technologies USA Inc. (https://www.zend.com) * @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License */ +declare(strict_types=1); + namespace Zend\Expressive; if (false === ($paths = getenv('PATH'))) { diff --git a/composer.json b/composer.json index d5c2e5c5..96b8fe7f 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "psr", "psr-7", "psr-11", + "expressive", "zf", "zendframework", "zend-expressive" @@ -21,39 +22,47 @@ "forum": "https://discourse.zendframework.com/c/questions/expressive" }, "require": { - "php": "^5.6 || ^7.0", + "php": "^7.1", "fig/http-message-util": "^1.1.2", - "http-interop/http-middleware": "^0.4.1", "psr/container": "^1.0", "psr/http-message": "^1.0.1", - "zendframework/zend-diactoros": "^1.3.10", - "zendframework/zend-expressive-router": "^2.4.1", - "zendframework/zend-expressive-template": "^1.0.4", - "zendframework/zend-stratigility": "^2.2.0" + "psr/http-server-middleware": "^1.0", + "zendframework/zend-expressive-router": "^3.0.0rc4", + "zendframework/zend-expressive-template": "^2.0.0alpha1", + "zendframework/zend-httphandlerrunner": "^1.0.1", + "zendframework/zend-stratigility": "^3.0.0rc1" }, "require-dev": { - "filp/whoops": "^2.1.6 || ^1.1.10", - "malukenho/docheader": "^0.1.5", + "filp/whoops": "^1.1.10 || ^2.1.13", + "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-strict-rules": "^0.9", + "phpunit/phpunit": "^7.0.1", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-expressive-aurarouter": "^2.2", - "zendframework/zend-expressive-fastroute": "^2.2", - "zendframework/zend-expressive-zendrouter": "^2.2", - "zendframework/zend-servicemanager": "^3.3 || ^2.7.8" + "zendframework/zend-diactoros": "^1.7.1", + "zendframework/zend-expressive-aurarouter": "^3.0.0rc3", + "zendframework/zend-expressive-fastroute": "^3.0.0rc4", + "zendframework/zend-expressive-zendrouter": "^3.0.0rc3", + "zendframework/zend-servicemanager": "^2.7.8 || ^3.3" }, "conflict": { - "container-interop/container-interop": "<1.2.0" + "container-interop/container-interop": "<1.2.0", + "zendframework/zend-diactoros": "<1.7.1" }, "suggest": { "filp/whoops": "^2.1 to use the Whoops error handler", + "psr/http-message-implementation": "Please install a psr/http-message-implementation to consume Expressive; e.g., zendframework/zend-diactoros", + "zendframework/zend-auradi-config": "^1.0 to use Aura.Di dependency injection container", "zendframework/zend-expressive-helpers": "^3.0 for its UrlHelper, ServerUrlHelper, and BodyParseMiddleware", - "aura/di": "^3.2 to make use of Aura.Di dependency injection container", - "xtreamwayz/pimple-container-interop": "^1.0 to use Pimple for dependency injection", "zendframework/zend-expressive-tooling": "For migration and development tools; require it with the --dev flag", + "zendframework/zend-pimple-config": "^1.0 to use Pimple for dependency injection container", "zendframework/zend-servicemanager": "^3.3 to use zend-servicemanager for dependency injection" }, "autoload": { + "files": [ + "src/constants.php" + ], "psr-4": { "Zend\\Expressive\\": "src/" } @@ -61,10 +70,7 @@ "autoload-dev": { "psr-4": { "ZendTest\\Expressive\\": "test/" - }, - "files": [ - "test/class_exists.php" - ] + } }, "config": { "sort-packages": true @@ -90,6 +96,7 @@ ], "cs-check": "phpcs", "cs-fix": "phpcbf", + "phpstan": "phpstan analyze -l max -c phpstan.neon ./src", "test": "phpunit --colors=always", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "license-check": "docheader check src/ test/" diff --git a/composer.lock b/composer.lock index 8cd6e07f..534ee968 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "43ef3ad68c22086ad9fa309ae57ea601", + "content-hash": "ac2230b7c744135cbf3ac7e2673317b9", "packages": [ { "name": "fig/http-message-util", @@ -56,59 +56,6 @@ ], "time": "2017-02-09T16:10:21+00:00" }, - { - "name": "http-interop/http-middleware", - "version": "0.4.1", - "source": { - "type": "git", - "url": "https://github.com/http-interop/http-middleware.git", - "reference": "9a801fe60e70d5d608b61d56b2dcde29516c81b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/http-interop/http-middleware/zipball/9a801fe60e70d5d608b61d56b2dcde29516c81b9", - "reference": "9a801fe60e70d5d608b61d56b2dcde29516c81b9", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Interop\\Http\\ServerMiddleware\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "factory", - "http", - "middleware", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "abandoned": "http-interop/http-server-middleware", - "time": "2017-01-14T15:23:42+00:00" - }, { "name": "psr/container", "version": "1.0.0", @@ -209,97 +156,110 @@ "time": "2016-08-06T14:39:51+00:00" }, { - "name": "webimpress/composer-extra-dependency", - "version": "0.2.2", + "name": "psr/http-server-handler", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/webimpress/composer-extra-dependency.git", - "reference": "31fa56391d30f03b1180c87610cbe22254780ad9" + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "439d92054dc06097f2406ec074a2627839955a02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webimpress/composer-extra-dependency/zipball/31fa56391d30f03b1180c87610cbe22254780ad9", - "reference": "31fa56391d30f03b1180c87610cbe22254780ad9", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/439d92054dc06097f2406ec074a2627839955a02", + "reference": "439d92054dc06097f2406ec074a2627839955a02", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1", - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "composer/composer": "^1.5.2", - "mikey179/vfsstream": "^1.6.5", - "phpunit/phpunit": "^5.7.22 || ^6.4.1", - "zendframework/zend-coding-standard": "~1.0.0" + "php": ">=7.0", + "psr/http-message": "^1.0" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "Webimpress\\ComposerExtraDependency\\Plugin" + "branch-alias": { + "dev-master": "1.0.x-dev" + } }, "autoload": { "psr-4": { - "Webimpress\\ComposerExtraDependency\\": "src/" + "Psr\\Http\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } ], - "description": "Composer plugin to require extra dependencies", - "homepage": "https://github.com/webimpress/composer-extra-dependency", + "description": "Common interface for HTTP server-side request handler", "keywords": [ - "composer", - "dependency", - "webimpress" + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" ], - "time": "2017-10-17T17:15:14+00:00" + "time": "2018-01-22T17:04:15+00:00" }, { - "name": "webimpress/http-middleware-compatibility", - "version": "0.1.4", + "name": "psr/http-server-middleware", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/webimpress/http-middleware-compatibility.git", - "reference": "8ed1c2c7523dce0035b98bc4f3a73ca9cd1d3717" + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webimpress/http-middleware-compatibility/zipball/8ed1c2c7523dce0035b98bc4f3a73ca9cd1d3717", - "reference": "8ed1c2c7523dce0035b98bc4f3a73ca9cd1d3717", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/ea17eb1fb2c8df6db919cc578451a8013c6a0ae5", + "reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5", "shasum": "" }, "require": { - "http-interop/http-middleware": "^0.1.1 || ^0.2 || ^0.3 || ^0.4.1 || ^0.5", - "php": "^5.6 || ^7.0", - "webimpress/composer-extra-dependency": "^0.2.2" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3" + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" }, "type": "library", "extra": { - "dependency": [ - "http-interop/http-middleware" - ] + "branch-alias": { + "dev-master": "1.0.x-dev" + } }, "autoload": { - "files": [ - "autoload/http-middleware.php" - ] + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], - "description": "Compatibility library for Draft PSR-15 HTTP Middleware", - "homepage": "https://github.com/webimpress/http-middleware-compatibility", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", "keywords": [ + "http", + "http-interop", "middleware", + "psr", "psr-15", - "webimpress" + "psr-7", + "request", + "response" ], - "abandoned": "psr/http-server-middleware", - "time": "2017-10-17T17:31:10+00:00" + "time": "2018-01-22T17:08:31+00:00" }, { "name": "zendframework/zend-diactoros", @@ -399,41 +359,43 @@ }, { "name": "zendframework/zend-expressive-router", - "version": "2.4.1", + "version": "3.0.0rc4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-expressive-router.git", - "reference": "e1a00596aa20a29968bdc6ecdf0256c8bfd6e0b5" + "reference": "3b21fb4e1b568bfa8b0ae699b6f0bdd5d5644863" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-expressive-router/zipball/e1a00596aa20a29968bdc6ecdf0256c8bfd6e0b5", - "reference": "e1a00596aa20a29968bdc6ecdf0256c8bfd6e0b5", + "url": "https://api.github.com/repos/zendframework/zend-expressive-router/zipball/3b21fb4e1b568bfa8b0ae699b6f0bdd5d5644863", + "reference": "3b21fb4e1b568bfa8b0ae699b6f0bdd5d5644863", "shasum": "" }, "require": { "fig/http-message-util": "^1.1.2", - "php": "^5.6 || ^7.0", + "php": "^7.1", "psr/container": "^1.0", "psr/http-message": "^1.0.1", - "webimpress/http-middleware-compatibility": "^0.1.1" + "psr/http-server-middleware": "^1.0" }, "require-dev": { - "http-interop/http-middleware": "0.4.1", - "malukenho/docheader": "^0.1.5", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0" + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^6.5.5", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-diactoros": "^1.7", + "zendframework/zend-stratigility": "^3.0.0rc1" }, "suggest": { - "zendframework/zend-expressive-aurarouter": "^1.0 to use the Aura.Router routing adapter", - "zendframework/zend-expressive-fastroute": "^1.2 to use the FastRoute routing adapter", - "zendframework/zend-expressive-zendrouter": "^1.2 to use the zend-router routing adapter" + "zendframework/zend-expressive-aurarouter": "^3.0 to use the Aura.Router routing adapter", + "zendframework/zend-expressive-fastroute": "^3.0 to use the FastRoute routing adapter", + "zendframework/zend-expressive-zendrouter": "^3.0 to use the zend-router routing adapter" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "2.3.x-dev", + "dev-develop": "2.4.x-dev", + "dev-release-3.0.0": "3.0.x-dev" }, "zf": { "config-provider": "Zend\\Expressive\\Router\\ConfigProvider" @@ -444,87 +406,56 @@ "Zend\\Expressive\\Router\\": "src/" } }, - "autoload-dev": { - "psr-4": { - "ZendTest\\Expressive\\Router\\": "test/" - } - }, - "scripts": { - "check": [ - "@license-check", - "@cs-check", - "@test" - ], - "cs-check": [ - "phpcs" - ], - "cs-fix": [ - "phpcbf" - ], - "test": [ - "phpunit --colors=always" - ], - "test-coverage": [ - "phpunit --colors=always --coverage-clover clover.xml" - ], - "license-check": [ - "docheader check src/ test/" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "description": "Router subcomponent for Expressive", "keywords": [ + "ZendFramework", "expressive", "http", "middleware", "psr", "psr-7", "zend-expressive", - "zendframework", "zf" ], - "support": { - "issues": "https://github.com/zendframework/zend-expressive-router/issues", - "source": "https://github.com/zendframework/zend-expressive-router", - "rss": "https://github.com/zendframework/zend-expressive-router/releases.atom", - "slack": "https://zendframework-slack.herokuapp.com", - "forum": "https://discourse.zendframework.com/c/questions/expressive" - }, - "time": "2018-03-08T19:27:02+00:00" + "time": "2018-03-07T16:56:58+00:00" }, { "name": "zendframework/zend-expressive-template", - "version": "1.0.4", + "version": "2.0.0alpha1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-expressive-template.git", - "reference": "23922f96b32ab6e64fc551ec06b81fd047828765" + "reference": "64a8c472eda24926fb1a9466e88ba2a31198de83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-expressive-template/zipball/23922f96b32ab6e64fc551ec06b81fd047828765", - "reference": "23922f96b32ab6e64fc551ec06b81fd047828765", + "url": "https://api.github.com/repos/zendframework/zend-expressive-template/zipball/64a8c472eda24926fb1a9466e88ba2a31198de83", + "reference": "64a8c472eda24926fb1a9466e88ba2a31198de83", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.7", + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^6.5.3", "zendframework/zend-coding-standard": "~1.0.0" }, "suggest": { - "zendframework/zend-expressive-platesrenderer": "^0.1 to use the Plates template renderer", - "zendframework/zend-expressive-twigrenderer": "^0.1 to use the Twig template renderer", - "zendframework/zend-expressive-zendviewrenderer": "^0.1 to use the zend-view PhpRenderer template renderer" + "zendframework/zend-expressive-platesrenderer": "^2.0 to use the Plates template renderer", + "zendframework/zend-expressive-twigrenderer": "^2.0 to use the Twig template renderer", + "zendframework/zend-expressive-zendviewrenderer": "^2.0 to use the zend-view PhpRenderer template renderer" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev", - "dev-develop": "1.1-dev" + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev", + "dev-release-2.0.0": "2.0.x-dev" } }, "autoload": { @@ -538,36 +469,97 @@ ], "description": "Template subcomponent for Expressive", "keywords": [ + "ZendFramework", "expressive", - "template" + "template", + "zend-expressive", + "zf" ], - "time": "2017-01-11T18:42:34+00:00" + "time": "2018-02-06T20:09:18+00:00" + }, + { + "name": "zendframework/zend-httphandlerrunner", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-httphandlerrunner.git", + "reference": "5e4c1e82a8bb1585020eafd32c49ece5a6ee98df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-httphandlerrunner/zipball/5e4c1e82a8bb1585020eafd32c49ece5a6ee98df", + "reference": "5e4c1e82a8bb1585020eafd32c49ece5a6ee98df", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "psr/http-message-implementation": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.3", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-diactoros": "^1.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "zf": { + "config-provider": "Zend\\HttpHandlerRunner\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Zend\\HttpHandlerRunner\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Execute PSR-15 RequestHandlerInterface instances and emit responses they generate.", + "keywords": [ + "ZendFramework", + "components", + "expressive", + "psr-15", + "psr-7", + "zf" + ], + "time": "2018-02-21T20:33:02+00:00" }, { "name": "zendframework/zend-stratigility", - "version": "2.2.0", + "version": "3.0.0rc1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stratigility.git", - "reference": "e8c413fcba926ede63099936a5f86acf9b8156c5" + "reference": "6e76a8bd5b50d1cbd0b7f702a6f61293290b4382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stratigility/zipball/e8c413fcba926ede63099936a5f86acf9b8156c5", - "reference": "e8c413fcba926ede63099936a5f86acf9b8156c5", + "url": "https://api.github.com/repos/zendframework/zend-stratigility/zipball/6e76a8bd5b50d1cbd0b7f702a6f61293290b4382", + "reference": "6e76a8bd5b50d1cbd0b7f702a6f61293290b4382", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", + "fig/http-message-util": "^1.1", + "php": "^7.1", "psr/http-message": "^1.0", - "webimpress/http-middleware-compatibility": "^0.1.4", + "psr/http-server-middleware": "^1.0", "zendframework/zend-escaper": "^2.3" }, + "conflict": { + "zendframework/zend-diactoros": "<1.7.1" + }, "require-dev": { - "malukenho/docheader": "^0.1.5", - "phpunit/phpunit": "^5.7.22 || ^6.4.1", + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^7.0.1", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-diactoros": "^1.0" + "zendframework/zend-diactoros": "^1.7.1" }, "suggest": { "psr/http-message-implementation": "Please install a psr/http-message-implementation to consume Stratigility; e.g., zendframework/zend-diactoros" @@ -575,13 +567,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "3.0-dev", + "dev-develop": "3.1-dev", + "dev-release-3.0.0": "3.0.x-dev" } }, "autoload": { "files": [ "src/functions/double-pass-middleware.php", + "src/functions/host.php", "src/functions/middleware.php", "src/functions/path.php" ], @@ -593,16 +587,16 @@ "license": [ "BSD-3-Clause" ], - "description": "Middleware for PHP", - "homepage": "https://github.com/zendframework/zend-stratigility", + "description": "PSR-7 middleware foundation for building and dispatching middleware pipelines", "keywords": [ "ZendFramework", "http", "middleware", + "psr-15", "psr-7", "zf" ], - "time": "2018-03-12T21:04:19+00:00" + "time": "2018-02-26T15:56:54+00:00" } ], "packages-dev": [ @@ -849,6 +843,57 @@ ], "time": "2016-01-20T08:20:44+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "d457344b6a035ef99236bdda4729ad7eeb233f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/d457344b6a035ef99236bdda4729ad7eeb233f54", + "reference": "d457344b6a035ef99236bdda4729ad7eeb233f54", + "shasum": "" + }, + "require": { + "ocramius/package-versions": "^1.2.0", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/pretty-package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "time": "2018-01-21T13:54:22+00:00" + }, { "name": "malukenho/docheader", "version": "0.1.7", @@ -935,80 +980,544 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2017-10-06T16:20:43+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "nette/bootstrap", + "version": "v2.4.5", + "source": { + "type": "git", + "url": "https://github.com/nette/bootstrap.git", + "reference": "804925787764d708a7782ea0d9382a310bb21968" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/804925787764d708a7782ea0d9382a310bb21968", + "reference": "804925787764d708a7782ea0d9382a310bb21968", + "shasum": "" + }, + "require": { + "nette/di": "~2.4.7", + "nette/utils": "~2.4", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "latte/latte": "~2.2", + "nette/application": "~2.3", + "nette/caching": "~2.3", + "nette/database": "~2.3", + "nette/forms": "~2.3", + "nette/http": "~2.4.0", + "nette/mail": "~2.3", + "nette/robot-loader": "^2.4.2 || ^3.0", + "nette/safe-stream": "~2.2", + "nette/security": "~2.3", + "nette/tester": "~2.0", + "tracy/tracy": "^2.4.1" + }, + "suggest": { + "nette/robot-loader": "to use Configurator::createRobotLoader()", + "tracy/tracy": "to use Configurator::enableTracy()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", + "homepage": "https://nette.org", + "keywords": [ + "bootstrapping", + "configurator", + "nette" + ], + "time": "2017-08-20T17:36:59+00:00" + }, + { + "name": "nette/di", + "version": "v2.4.10", + "source": { + "type": "git", + "url": "https://github.com/nette/di.git", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/di/zipball/a4b3be935b755f23aebea1ce33d7e3c832cdff98", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nette/neon": "^2.3.3 || ~3.0.0", + "nette/php-generator": "^2.6.1 || ~3.0.0", + "nette/utils": "^2.4.3 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/bootstrap": "<2.4", + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "homepage": "https://nette.org", + "keywords": [ + "compiled", + "di", + "dic", + "factory", + "ioc", + "nette", + "static" + ], + "time": "2017-08-31T22:42:00+00:00" + }, + { + "name": "nette/finder", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/nette/finder.git", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/finder/zipball/4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "Nette Finder: Files Searching", + "homepage": "https://nette.org", + "time": "2017-07-10T23:47:08+00:00" + }, + { + "name": "nette/neon", + "version": "v2.4.2", + "source": { + "type": "git", + "url": "https://github.com/nette/neon.git", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/neon/zipball/9eacd50553b26b53a3977bfb2fea2166d4331622", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "Nette NEON: parser & generator for Nette Object Notation", + "homepage": "http://ne-on.org", + "time": "2017-07-11T18:29:08+00:00" + }, + { + "name": "nette/php-generator", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "1652635d312a8db4291b16f3ebf87cb1a15a6257" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/1652635d312a8db4291b16f3ebf87cb1a15a6257", + "reference": "1652635d312a8db4291b16f3ebf87cb1a15a6257", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.2 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "time": "2017-09-26T11:19:32+00:00" + }, + { + "name": "nette/robot-loader", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/nette/robot-loader.git", + "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", + "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nette/finder": "^2.3 || ^3.0", + "nette/utils": "^2.4 || ^3.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" }, { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/mockery/mockery", + "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "homepage": "https://nette.org", "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" + "autoload", + "class", + "interface", + "nette", + "trait" ], - "time": "2017-10-06T16:20:43+00:00" + "time": "2017-09-26T13:42:21+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.7.0", + "name": "nette/utils", + "version": "v2.5.1", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "url": "https://github.com/nette/utils.git", + "reference": "8a85ce76298c8a8941f912b8fa3ee93ca17d2ebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/nette/utils/zipball/8a85ce76298c8a8941f912b8fa3ee93ca17d2ebc", + "reference": "8a85ce76298c8a8941f912b8fa3ee93ca17d2ebc", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "for script transliteration in Strings::webalize() and toAscii()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, + "classmap": [ + "src/" + ], "files": [ - "src/DeepCopy/deep_copy.php" + "src/loader.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } ], - "time": "2017-10-19T19:58:43+00:00" + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "time": "2018-02-19T14:42:42+00:00" }, { "name": "nikic/fast-route", @@ -1056,6 +1565,106 @@ ], "time": "2018-02-13T20:26:39+00:00" }, + { + "name": "nikic/php-parser", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-02-28T20:30:58+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": "^7.1.0" + }, + "require-dev": { + "composer/composer": "^1.6.3", + "ext-zip": "*", + "infection/infection": "^0.7.1", + "phpunit/phpunit": "^7.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2018-02-05T13:05:30+00:00" + }, { "name": "phar-io/manifest", "version": "1.0.1", @@ -1373,42 +1982,194 @@ ], "time": "2018-02-19T10:16:54+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/02f909f134fe06f0cd4790d8627ee24efbe84d6a", + "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a", + "shasum": "" + }, + "require": { + "php": "~7.0" + }, + "require-dev": { + "consistence/coding-standard": "^2.0.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan": "^0.9", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^3.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "time": "2018-01-13T18:19:41+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.9.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "e59541bcc7cac9b35ca54db6365bf377baf4a488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e59541bcc7cac9b35ca54db6365bf377baf4a488", + "reference": "e59541bcc7cac9b35ca54db6365bf377baf4a488", + "shasum": "" + }, + "require": { + "jean85/pretty-package-versions": "^1.0.3", + "nette/bootstrap": "^2.4 || ^3.0", + "nette/di": "^2.4.7 || ^3.0", + "nette/robot-loader": "^3.0.1", + "nette/utils": "^2.4.5 || ^3.0", + "nikic/php-parser": "^3.1", + "php": "~7.0", + "phpstan/phpdoc-parser": "^0.2", + "symfony/console": "~3.2 || ~4.0", + "symfony/finder": "~3.2 || ~4.0" + }, + "require-dev": { + "consistence/coding-standard": "2.2.1", + "ext-gd": "*", + "ext-intl": "*", + "ext-mysqli": "*", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan-php-parser": "^0.9", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpstan/phpstan-strict-rules": "^0.9", + "phpunit/phpunit": "^6.5.4", + "slevomat/coding-standard": "4.0.0" + }, + "bin": [ + "bin/phpstan" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": [ + "src/", + "build/PHPStan" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "time": "2018-01-28T13:22:19+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "0.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "15be9090622c6b85c079922308f831018d8d9e23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/15be9090622c6b85c079922308f831018d8d9e23", + "reference": "15be9090622c6b85c079922308f831018d8d9e23", + "shasum": "" + }, + "require": { + "php": "~7.0", + "phpstan/phpstan": "^0.9" + }, + "require-dev": { + "consistence/coding-standard": "^2.0.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan-phpunit": "^0.9", + "phpunit/phpunit": "^6.4", + "slevomat/coding-standard": "^3.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "time": "2017-11-26T20:12:30+00:00" + }, { "name": "phpunit/php-code-coverage", - "version": "5.3.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", + "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", + "php": "^7.1", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^7.0" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -1434,7 +2195,7 @@ "testing", "xunit" ], - "time": "2017-12-06T09:29:45+00:00" + "time": "2018-02-02T07:01:41+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1526,28 +2287,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1562,7 +2323,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1571,33 +2332,33 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2018-02-01T13:07:23+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1620,20 +2381,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2018-02-01T13:16:43+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.7", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6bd77b57707c236833d2b57b968e403df060c9d9" + "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6bd77b57707c236833d2b57b968e403df060c9d9", - "reference": "6bd77b57707c236833d2b57b968e403df060c9d9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", + "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", "shasum": "" }, "require": { @@ -1645,15 +2406,15 @@ "myclabs/deep-copy": "^1.6.1", "phar-io/manifest": "^1.0.1", "phar-io/version": "^1.0", - "php": "^7.0", + "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", + "phpunit/php-code-coverage": "^6.0", "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/php-timer": "^2.0", + "phpunit/phpunit-mock-objects": "^6.0", "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", + "sebastian/diff": "^3.0", "sebastian/environment": "^3.1", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", @@ -1661,16 +2422,12 @@ "sebastian/resource-operations": "^1.0", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0" }, "bin": [ "phpunit" @@ -1678,7 +2435,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -1704,33 +2461,30 @@ "testing", "xunit" ], - "time": "2018-02-26T07:01:09+00:00" + "time": "2018-02-26T07:03:12+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e3249dedc2d99259ccae6affbc2684eac37c2e53", + "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.5", - "php": "^7.0", + "php": "^7.1", "phpunit/php-text-template": "^1.2.1", "sebastian/exporter": "^3.1" }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^7.0" }, "suggest": { "ext-soap": "*" @@ -1738,7 +2492,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "6.0.x-dev" } }, "autoload": { @@ -1763,7 +2517,7 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-02-15T05:27:38+00:00" }, { "name": "psr/log", @@ -1923,28 +2677,29 @@ }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1969,9 +2724,12 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2018-02-01T13:45:15+00:00" }, { "name": "sebastian/environment", @@ -2746,35 +3504,41 @@ }, { "name": "zendframework/zend-expressive-aurarouter", - "version": "2.2.0", + "version": "3.0.0rc3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-expressive-aurarouter.git", - "reference": "cb0d8b343f11d32f3171c558c7e9e42c16ff5d3b" + "reference": "1ee5c5d9bbd658803dd0a42d5180e9413dc34164" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-expressive-aurarouter/zipball/cb0d8b343f11d32f3171c558c7e9e42c16ff5d3b", - "reference": "cb0d8b343f11d32f3171c558c7e9e42c16ff5d3b", + "url": "https://api.github.com/repos/zendframework/zend-expressive-aurarouter/zipball/1ee5c5d9bbd658803dd0a42d5180e9413dc34164", + "reference": "1ee5c5d9bbd658803dd0a42d5180e9413dc34164", "shasum": "" }, "require": { - "aura/router": "^3.0.1", + "aura/router": "^3.1", "fig/http-message-util": "^1.1.2", - "php": "^5.6 || ^7.0", + "php": "^7.1", "psr/http-message": "^1.0.1", - "zendframework/zend-expressive-router": "^2.4" + "zendframework/zend-expressive-router": "^3.0.0rc4" }, "require-dev": { - "malukenho/docheader": "^0.1.5", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0" + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-diactoros": "^1.7", + "zendframework/zend-stratigility": "^3.0.0rc1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "2.1.x-dev", + "dev-develop": "2.2.x-dev", + "dev-release-3.0.0": "3.0.x-dev" + }, + "zf": { + "config-provider": "Zend\\Expressive\\Router\\AuraRouter\\ConfigProvider" } }, "autoload": { @@ -2798,44 +3562,50 @@ "zend-expressive", "zf" ], - "time": "2018-03-08T17:22:09+00:00" + "time": "2018-03-07T17:14:23+00:00" }, { "name": "zendframework/zend-expressive-fastroute", - "version": "2.2.0", + "version": "3.0.0rc4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-expressive-fastroute.git", - "reference": "e35c040c7b76fd03156e537053b3c05c700028dc" + "reference": "43fac9a08c4eb8587899fb1c6bb916ec8925f5d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-expressive-fastroute/zipball/e35c040c7b76fd03156e537053b3c05c700028dc", - "reference": "e35c040c7b76fd03156e537053b3c05c700028dc", + "url": "https://api.github.com/repos/zendframework/zend-expressive-fastroute/zipball/43fac9a08c4eb8587899fb1c6bb916ec8925f5d2", + "reference": "43fac9a08c4eb8587899fb1c6bb916ec8925f5d2", "shasum": "" }, "require": { "fig/http-message-util": "^1.1.2", "nikic/fast-route": "^1.2", - "php": "^5.6 || ^7.0", + "php": "^7.1", "psr/container": "^1.0", "psr/http-message": "^1.0.1", - "zendframework/zend-expressive-router": "^2.4", - "zendframework/zend-stdlib": "^3.1 || 2.*" + "zendframework/zend-expressive-router": "^3.0.0rc4", + "zendframework/zend-stdlib": "^2.0 || ^3.1" }, "conflict": { "container-interop/container-interop": "<1.2.0" }, "require-dev": { - "malukenho/docheader": "^0.1.5", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0" + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-diactoros": "^1.7", + "zendframework/zend-stratigility": "^3.0.0rc1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "2.1-dev", + "dev-develop": "2.2-dev", + "dev-release-3.0.0": "3.0.x-dev" + }, + "zf": { + "config-provider": "Zend\\Expressive\\Router\\FastRouteRouter\\ConfigProvider" } }, "autoload": { @@ -2859,41 +3629,46 @@ "zend-expressive", "zf" ], - "time": "2018-03-08T16:09:36+00:00" + "time": "2018-03-07T17:10:51+00:00" }, { "name": "zendframework/zend-expressive-zendrouter", - "version": "2.2.0", + "version": "3.0.0rc3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-expressive-zendrouter.git", - "reference": "c7edcd47eb22842f7120432d068ef832077352d9" + "reference": "0a03edf71e89a78efee2fe18bfa092597738dbec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-expressive-zendrouter/zipball/c7edcd47eb22842f7120432d068ef832077352d9", - "reference": "c7edcd47eb22842f7120432d068ef832077352d9", + "url": "https://api.github.com/repos/zendframework/zend-expressive-zendrouter/zipball/0a03edf71e89a78efee2fe18bfa092597738dbec", + "reference": "0a03edf71e89a78efee2fe18bfa092597738dbec", "shasum": "" }, "require": { "fig/http-message-util": "^1.1.2", - "php": "^5.6 || ^7.0", + "php": "^7.1", "psr/http-message": "^1.0.1", - "zendframework/zend-expressive-router": "^2.4", + "zendframework/zend-expressive-router": "^3.0.0rc3", "zendframework/zend-psr7bridge": "^0.2.2 || ^1.0.0", "zendframework/zend-router": "^3.0.2" }, "require-dev": { - "malukenho/docheader": "^0.1.5", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "malukenho/docheader": "^0.1.6", + "phpunit/phpunit": "^7.0.2", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-i18n": "^2.7.3" + "zendframework/zend-i18n": "^2.7.4", + "zendframework/zend-stratigility": "^3.0.0rc1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev", - "dev-develop": "3.0.x-dev" + "dev-master": "2.1.x-dev", + "dev-develop": "2.2.x-dev", + "dev-release-3.0.0": "3.0.x-dev" + }, + "zf": { + "config-provider": "Zend\\Expressive\\Router\\ZendRouter\\ConfigProvider" } }, "autoload": { @@ -2916,7 +3691,7 @@ "zend-expressive", "zf" ], - "time": "2018-03-08T17:32:50+00:00" + "time": "2018-03-07T17:17:26+00:00" }, { "name": "zendframework/zend-http", @@ -3361,11 +4136,18 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "zendframework/zend-expressive-router": 5, + "zendframework/zend-expressive-template": 15, + "zendframework/zend-stratigility": 5, + "zendframework/zend-expressive-aurarouter": 5, + "zendframework/zend-expressive-fastroute": 5, + "zendframework/zend-expressive-zendrouter": 5 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^5.6 || ^7.0" + "php": "^7.1" }, "platform-dev": [] } diff --git a/docs/book/cookbook/autowiring-routes-and-pipelines.md b/docs/book/cookbook/autowiring-routes-and-pipelines.md index ee20e0df..edc157f3 100644 --- a/docs/book/cookbook/autowiring-routes-and-pipelines.md +++ b/docs/book/cookbook/autowiring-routes-and-pipelines.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/common-prefix-for-routes.md b/docs/book/cookbook/common-prefix-for-routes.md index 33a493b7..80c7964e 100644 --- a/docs/book/cookbook/common-prefix-for-routes.md +++ b/docs/book/cookbook/common-prefix-for-routes.md @@ -1,8 +1,8 @@ - + diff --git a/docs/book/cookbook/debug-toolbars.md b/docs/book/cookbook/debug-toolbars.md index 3b6208ea..17ff1707 100644 --- a/docs/book/cookbook/debug-toolbars.md +++ b/docs/book/cookbook/debug-toolbars.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/flash-messengers.md b/docs/book/cookbook/flash-messengers.md index 6d62fe8f..e8c89d55 100644 --- a/docs/book/cookbook/flash-messengers.md +++ b/docs/book/cookbook/flash-messengers.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/passing-data-between-middleware.md b/docs/book/cookbook/passing-data-between-middleware.md index 4000aa98..84120a6b 100644 --- a/docs/book/cookbook/passing-data-between-middleware.md +++ b/docs/book/cookbook/passing-data-between-middleware.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/route-specific-pipeline.md b/docs/book/cookbook/route-specific-pipeline.md index f816821e..c12594ff 100644 --- a/docs/book/cookbook/route-specific-pipeline.md +++ b/docs/book/cookbook/route-specific-pipeline.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/setting-locale-depending-routing-parameter.md b/docs/book/cookbook/setting-locale-depending-routing-parameter.md index 4af73769..4bab0a7b 100644 --- a/docs/book/cookbook/setting-locale-depending-routing-parameter.md +++ b/docs/book/cookbook/setting-locale-depending-routing-parameter.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/setting-locale-without-routing-parameter.md b/docs/book/cookbook/setting-locale-without-routing-parameter.md index 734cc7b6..feb8ec3e 100644 --- a/docs/book/cookbook/setting-locale-without-routing-parameter.md +++ b/docs/book/cookbook/setting-locale-without-routing-parameter.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/using-a-base-path.md b/docs/book/cookbook/using-a-base-path.md index 0ca0c006..ec6c8f48 100644 --- a/docs/book/cookbook/using-a-base-path.md +++ b/docs/book/cookbook/using-a-base-path.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/using-custom-view-helpers.md b/docs/book/cookbook/using-custom-view-helpers.md index 24bf8052..567ea8fa 100644 --- a/docs/book/cookbook/using-custom-view-helpers.md +++ b/docs/book/cookbook/using-custom-view-helpers.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/using-routed-middleware-class-as-controller.md b/docs/book/cookbook/using-routed-middleware-class-as-controller.md index 5ee1aa68..7cfbc026 100644 --- a/docs/book/cookbook/using-routed-middleware-class-as-controller.md +++ b/docs/book/cookbook/using-routed-middleware-class-as-controller.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/cookbook/using-zend-form-view-helpers.md b/docs/book/cookbook/using-zend-form-view-helpers.md index 803ddfdb..04e0d841 100644 --- a/docs/book/cookbook/using-zend-form-view-helpers.md +++ b/docs/book/cookbook/using-zend-form-view-helpers.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/application.md b/docs/book/features/application.md index 86c699e8..ceba35b8 100644 --- a/docs/book/features/application.md +++ b/docs/book/features/application.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/aura-di.md b/docs/book/features/container/aura-di.md index 46d367af..8c8c63e6 100644 --- a/docs/book/features/container/aura-di.md +++ b/docs/book/features/container/aura-di.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/delegator-factories.md b/docs/book/features/container/delegator-factories.md index e5ec6587..24209f41 100644 --- a/docs/book/features/container/delegator-factories.md +++ b/docs/book/features/container/delegator-factories.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/factories.md b/docs/book/features/container/factories.md index 19e48de2..a33da2bd 100644 --- a/docs/book/features/container/factories.md +++ b/docs/book/features/container/factories.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/intro.md b/docs/book/features/container/intro.md index 36065673..66910650 100644 --- a/docs/book/features/container/intro.md +++ b/docs/book/features/container/intro.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/pimple.md b/docs/book/features/container/pimple.md index 63357718..0dc3a915 100644 --- a/docs/book/features/container/pimple.md +++ b/docs/book/features/container/pimple.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/container/zend-servicemanager.md b/docs/book/features/container/zend-servicemanager.md index 6d04cb97..eb1daca6 100644 --- a/docs/book/features/container/zend-servicemanager.md +++ b/docs/book/features/container/zend-servicemanager.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/emitters.md b/docs/book/features/emitters.md index 7f8c62df..fb29953a 100644 --- a/docs/book/features/emitters.md +++ b/docs/book/features/emitters.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/error-handling.md b/docs/book/features/error-handling.md index 149a1f2d..182afb4c 100644 --- a/docs/book/features/error-handling.md +++ b/docs/book/features/error-handling.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/helpers/body-parse.md b/docs/book/features/helpers/body-parse.md index 2161ecac..ea44d39e 100644 --- a/docs/book/features/helpers/body-parse.md +++ b/docs/book/features/helpers/body-parse.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/helpers/content-length.md b/docs/book/features/helpers/content-length.md index 6c357f54..1ca04357 100644 --- a/docs/book/features/helpers/content-length.md +++ b/docs/book/features/helpers/content-length.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/helpers/intro.md b/docs/book/features/helpers/intro.md index d2260cc8..a2503c5a 100644 --- a/docs/book/features/helpers/intro.md +++ b/docs/book/features/helpers/intro.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/helpers/server-url-helper.md b/docs/book/features/helpers/server-url-helper.md index 4252ab83..67585a7d 100644 --- a/docs/book/features/helpers/server-url-helper.md +++ b/docs/book/features/helpers/server-url-helper.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/helpers/url-helper.md b/docs/book/features/helpers/url-helper.md index f9e0b608..6170b241 100644 --- a/docs/book/features/helpers/url-helper.md +++ b/docs/book/features/helpers/url-helper.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/middleware-types.md b/docs/book/features/middleware-types.md index 5b171c17..0ea1e3cb 100644 --- a/docs/book/features/middleware-types.md +++ b/docs/book/features/middleware-types.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/middleware/implicit-methods-middleware.md b/docs/book/features/middleware/implicit-methods-middleware.md index 53c5f6d9..3fa5fbdc 100644 --- a/docs/book/features/middleware/implicit-methods-middleware.md +++ b/docs/book/features/middleware/implicit-methods-middleware.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/modular-applications.md b/docs/book/features/modular-applications.md index 5f36ce8f..4769bc3a 100644 --- a/docs/book/features/modular-applications.md +++ b/docs/book/features/modular-applications.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/aura.md b/docs/book/features/router/aura.md index bb67cd0a..6dd65ffc 100644 --- a/docs/book/features/router/aura.md +++ b/docs/book/features/router/aura.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/fast-route.md b/docs/book/features/router/fast-route.md index fea79dd2..1a2e354a 100644 --- a/docs/book/features/router/fast-route.md +++ b/docs/book/features/router/fast-route.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/interface.md b/docs/book/features/router/interface.md index 6e10e4bc..737c61fb 100644 --- a/docs/book/features/router/interface.md +++ b/docs/book/features/router/interface.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/intro.md b/docs/book/features/router/intro.md index 2c4e740f..b598a8c5 100644 --- a/docs/book/features/router/intro.md +++ b/docs/book/features/router/intro.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/piping.md b/docs/book/features/router/piping.md index cae4a12f..96e15412 100644 --- a/docs/book/features/router/piping.md +++ b/docs/book/features/router/piping.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/uri-generation.md b/docs/book/features/router/uri-generation.md index ce2436dc..cfaa71a0 100644 --- a/docs/book/features/router/uri-generation.md +++ b/docs/book/features/router/uri-generation.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/router/zf2.md b/docs/book/features/router/zf2.md index 3359c623..603fe9aa 100644 --- a/docs/book/features/router/zf2.md +++ b/docs/book/features/router/zf2.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/interface.md b/docs/book/features/template/interface.md index 07843e20..49bef67a 100644 --- a/docs/book/features/template/interface.md +++ b/docs/book/features/template/interface.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/intro.md b/docs/book/features/template/intro.md index a10369d2..b35d6533 100644 --- a/docs/book/features/template/intro.md +++ b/docs/book/features/template/intro.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/middleware.md b/docs/book/features/template/middleware.md index e14904d0..66f840b3 100644 --- a/docs/book/features/template/middleware.md +++ b/docs/book/features/template/middleware.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/plates.md b/docs/book/features/template/plates.md index ce9c5657..32e634e4 100644 --- a/docs/book/features/template/plates.md +++ b/docs/book/features/template/plates.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/twig.md b/docs/book/features/template/twig.md index bad0f8c4..d3eda7fb 100644 --- a/docs/book/features/template/twig.md +++ b/docs/book/features/template/twig.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/features/template/zend-view.md b/docs/book/features/template/zend-view.md index 5d4d70b1..dbf48908 100644 --- a/docs/book/features/template/zend-view.md +++ b/docs/book/features/template/zend-view.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/getting-started/features.md b/docs/book/getting-started/features.md index 16996869..193df5cb 100644 --- a/docs/book/getting-started/features.md +++ b/docs/book/getting-started/features.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/getting-started/skeleton.md b/docs/book/getting-started/skeleton.md index 0f347470..f36be001 100644 --- a/docs/book/getting-started/skeleton.md +++ b/docs/book/getting-started/skeleton.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/getting-started/standalone.md b/docs/book/getting-started/standalone.md index a4739304..f8dce4a5 100644 --- a/docs/book/getting-started/standalone.md +++ b/docs/book/getting-started/standalone.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/index.html b/docs/book/index.html index 73d38254..2cf82963 100644 --- a/docs/book/index.html +++ b/docs/book/index.html @@ -43,7 +43,7 @@
- Route requests to middleware using the routing library of your choice. + Route requests to middleware using the routing library of your choice.
@@ -58,7 +58,7 @@Make your code flexible and robust, using the - dependency injection container of your choice. + dependency injection container of your choice.
@@ -70,7 +70,7 @@- Create templated responses, using + Create templated responses, using a variety of template engines.
@@ -83,7 +83,7 @@- Handle errors gracefully, using + Handle errors gracefully, using templated error pages, whoops, or your own solution!
@@ -115,7 +115,7 @@
$pathMiddleware = function (
ServerRequestInterface $request,
- DelegateInterface $delegate
+ RequestHandlerInterface $handler
) {
$uri = $request->getUri();
$path = $uri->getPath();
@@ -141,15 +141,15 @@ Applications, Simplified
Or use the menu to navigate to the section you're interested in.
diff --git a/docs/book/reference/cli-tooling.md b/docs/book/reference/cli-tooling.md index 00ada7f9..07088bea 100644 --- a/docs/book/reference/cli-tooling.md +++ b/docs/book/reference/cli-tooling.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/reference/expressive-projects.md b/docs/book/reference/expressive-projects.md index 7a4d5ff2..fb932aa0 100644 --- a/docs/book/reference/expressive-projects.md +++ b/docs/book/reference/expressive-projects.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/reference/migration/to-v2.md b/docs/book/reference/migration/to-v2.md index b5e0bdbc..9c61ae2f 100644 --- a/docs/book/reference/migration/to-v2.md +++ b/docs/book/reference/migration/to-v2.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/reference/usage-examples.md b/docs/book/reference/usage-examples.md index 8e50eb86..15f6c537 100644 --- a/docs/book/reference/usage-examples.md +++ b/docs/book/reference/usage-examples.md @@ -1,6 +1,6 @@ - + diff --git a/docs/book/v3/cookbook/autowiring-routes-and-pipelines.md b/docs/book/v3/cookbook/autowiring-routes-and-pipelines.md new file mode 100644 index 00000000..7837368a --- /dev/null +++ b/docs/book/v3/cookbook/autowiring-routes-and-pipelines.md @@ -0,0 +1,210 @@ +# How can I autowire routes and pipelines? + +Expressive 2.0 switched to _programmatic_ pipelines and routes, versus +_configuration-driven_ pipelines and routing as used in version 1. One drawback +is that with configuration-driven approaches, users could provide configuration +via a module `ConfigProvider`, and automatically expose new pipeline middleware +or routes; with a programmatic approach, this is no longer possible. + +Or is it? + +## Delegator Factories + +One possibility available to version 2 applications is to use _delegator +factories_ on the `Zend\Expressive\Application` instance in order to inject +these items. + +A _delegator factory_ is a factory that _delegates_ creation of an instance to a +callback, and then operates on that instance for the purpose of altering the +instance or providing a replacement (e.g., a decorator or proxy). The delegate +callback usually wraps a service factory, or, because delegator factories +_also_ return an instance, additional delegator factories. As such, you assign +delegator _factories_, plural, to instances, allowing multiple delegator +factories to intercept processing of the service initialization. + +For the purposes of this particular example, we will use delegator factories to +both _pipe_ middleware as well as _route_ middleware. + +To demonstrate, we'll take the default pipeline and routing from the skeleton +application, and provide it via a delegator factory instead. + +First, we'll create the class `App\Factory\PipelineAndRoutesDelegator`, with +the following contents: + +```php +pipe(ErrorHandler::class); + $app->pipe(ServerUrlMiddleware::class); + $app->pipeRoutingMiddleware(); + $app->pipe(ImplicitHeadMiddleware::class); + $app->pipe(ImplicitOptionsMiddleware::class); + $app->pipe(UrlHelperMiddleware::class); + $app->pipeDispatchMiddleware(); + $app->pipe(NotFoundHandler::class); + + // Setup routes: + $app->get('/', Action\HomePageAction::class, 'home'); + $app->get('/api/ping', Action\PingAction::class, 'api.ping'); + + return $app; + } +} +``` + +> ### Where to put the factory +> +> You will place the factory class in one of the following locations: +> +> - `src/App/Factory/PipelineAndRoutesDelegator.php` if using the default, flat, +> application structure. +> - `src/App/src/Factory/PipelineAndRoutesDelegator.php` if using the +> recommended, modular, application structure. + +Once you've created this, edit the class `App\ConfigProvider`; in it, we'll +update the `getDependencies()` method to add the delegator factory: + +```php +public function getDependencies() +{ + return [ + /* . . . */ + 'delegators' => [ + \Zend\Expressive\Application::class => [ + Factory\PipelineAndRoutesDelegator::class, + ], + ], + ]; +} +``` + +> ### Where is the ConfigProvider class? +> +> The `ConfigProvider` class is in one of the following locations: +> +> - `src/App/ConfigProvider.php` if using the default, flat, application +> structure. +> - `src/App/src/ConfigProvider.php` using the recommended, modular, application +> structure. + +> ### Why is an array assigned? +> +> As noted above in the description of delegator factories, since each delegator +> factory returns an instance, you can nest multiple delegator factories in +> order to shape initialization of a service. As such, they are assigned as an +> _array_ to the service. + +Once you've done this, you can remove: + +- `config/pipeline.php` +- `config/routes.php` +- The following lines from `public/index.php`: + + ```php + // Import programmatic/declarative middleware pipeline and routing + // configuration statements + require 'config/pipeline.php'; + require 'config/routes.php'; + ``` + +If you reload your application at this point, you should see that everything +continues to work as expected! + +## Caution: pipelines + +Using delegator factories is a nice way to keep your routing and pipeline +configuration close to the modules in which they are defined. However, there is +a caveat: you likely should **not** register pipeline middleware in a delegator +factory _other than within your root application module_. + +The reason for this is simple: pipelines are linear, and specific to your +application. If one module pipes in middleware, there's no guarantee it will be +piped before or after your main pipeline, and no way to pipe the middleware at a +position in the middle of the pipeline! + +As such: + +- Use a `config/pipeline.php` file for your pipeline, **OR** +- Ensure you only define the pipeline in a **single** delegator factory on your + `Application` instance. + +## Caution: third-party, distributed modules + +If you are developing a module to distribute as a package via +[Composer](https://getcomposer.org/), **you should not autowire any delegator +factories that inject pipeline middleware or routes in the `Application`**. + +Why? + +As noted in the above section, pipelines should be created exactly once, at +the application level. Registering pipeline middleware within a distributable +package will very likely not have the intended consequences. + +If you ship with pipeline middleware, we suggest that you: + +- Document the middleware, and where you anticipate it being used in the + middleware pipeline. +- Document how to add the middleware service to dependency configuration, or + provide the dependency configuration via your module's `ConfigProvider`. + +With regards to routes, there are other considerations: + +- Routes defined by the package might conflict with the application, or with + other packages used by the application. + +- Routing definitions are typically highly specific to the router implementation + in use. As an example, each of the currently supported router implementations + has a different syntax for placeholders: + + - `/user/:id` + "constraints" configuration to define constraints (zend-router) + - `/user/{id}` + "tokens" configuration to define constraints (Aura.Router) + - `/user/{id:\d+}` (FastRoute) + +- Your application may have specific routing considerations or design. + +You could, of course, detect what router is in use, and provide routing for each +known, supported router implementation within your delegator factory. We even +recommend doing exactly that. However, we note that such an approach does not +solve the other two points above. + +However, we still recommend _shipping_ a delegator factory that would register +your routes, since routes *are* often a part of module design; just **do not +autowire** that delegator factory. This way, end-users who *can* use the +defaults do not need to cut-and-paste routing definitions from your +documentation into their own applications; they will instead opt-in to your +delegator factory by wiring it into their own configuration. + +## Synopsis + +- We recommend using delegator factories for the purpose of autowiring routes, + and, with caveats, pipeline middleware: + - The pipeline should be created exactly once, so calls to `pipe()` should + occur in exactly _one_ delegator factory. +- Distributable packages should create a delegator factory for _routes only_, + but _should not_ register the delegator factory by default. diff --git a/docs/book/v3/cookbook/common-prefix-for-routes.md b/docs/book/v3/cookbook/common-prefix-for-routes.md new file mode 100644 index 00000000..b8605ada --- /dev/null +++ b/docs/book/v3/cookbook/common-prefix-for-routes.md @@ -0,0 +1,46 @@ +# How can I prepend a common path to all my routes? + +You may have multiple middleware in your project, each providing their own +functionality: + +```php +$middleware1 = new UserMiddleware(); +$middleware2 = new ProjectMiddleware(); + +$app = AppFactory::create(); +$app->pipe($middleware1); +$app->pipe($middleware2); + +$app->run(); +``` + +Let's assume the above represents an API. + +As your application progresses, you may have a mixture of different content, and now want to have +the above segregated under the path `/api`. + +This is essentially the same problem as addressed in the +["Segregating your application to a subpath"](../reference/usage-examples.md#segregating-your-application-to-a-subpath) example. + +To accomplish it: + +- Create a new application. +- Pipe the previous application to the new one, under the path `/api`. + +```php +$middleware1 = new UserMiddleware(); +$middleware2 = new ProjectMiddleware(); + +$api = AppFactory::create(); +$api->pipe($middleware1); +$api->pipe($middleware2); + +$app = AppFactory::create(); +$app->pipe('/api', $api); + +$app->run(); +``` + +The above works, because every `Application` instance is itself middleware, and, more specifically, +an instance of [Stratigility's `MiddlewarePipe`](https://github.com/zendframework/zend-stratigility/blob/master/doc/book/middleware.md), +which provides the ability to compose middleware. diff --git a/docs/book/v3/cookbook/debug-toolbars.md b/docs/book/v3/cookbook/debug-toolbars.md new file mode 100644 index 00000000..b98ebfa3 --- /dev/null +++ b/docs/book/v3/cookbook/debug-toolbars.md @@ -0,0 +1,80 @@ +# How can I get a debug toolbar for my Expressive application? + +Many modern frameworks and applications provide debug toolbars: in-browser +toolbars to provide profiling information of the request executed. These can +provide invaluable details into application objects, database queries, and more. +As an Expressive user, how can you get similar functionality? + +## Zend Server Z-Ray + +[Zend Server](http://www.zend.com/en/products/zend_server) ships with a tool +called [Z-Ray](http://www.zend.com/en/products/server/z-ray), which provides +both a debug toolbar and debug console (for API debugging). Z-Ray is also +currently [available as a standalone technology +preview](http://www.zend.com/en/products/z-ray/z-ray-preview), and can be added +as an extension to an existing PHP installation. + +When using Zend Server or the standalone Z-Ray, you do not need to make any +changes to your application whatsoever to benefit from it; you simply need to +make sure Z-Ray is enabled and/or that you've setup a security token to +selectively enable it on-demand. See the +[Z-Ray documentation](http://files.zend.com/help/Zend-Server/content/z-ray_concept.htm) +for full usage details. + +## bitExpert/prophiler-psr7-middleware + +Another option is bitExpert's [prophiler-psr7-middleware](https://github.com/bitExpert/prophiler-psr7-middleware). +This package wraps [fabfuel/prophiler](https://github.com/fabfuel/prophiler), +which provides a PHP-based profiling tool and toolbar; the bitExpert package +wraps this in PSR-7 middleware to make consumption in those paradigms trivial. + +To add the toolbar middleware to your application, use composer: + +```bash +$ composer require bitExpert/prophiler-psr7-middleware +``` + +From there, you will need to create a factory for the middleware, and add it to +your middleware pipeline. Stephan Hochdörfer, author of the package, has written +a [post detailing these steps](https://blog.bitexpert.de/blog/using-prophiler-with-zend-expressive/). + +> ### Use locally! +> +> One minor change we recommend over the directions Stephan provides is that you +> configure the factory and middleware in the +> `config/autoload/middleware-pipeline.local.php` file, vs the `.global` version. +> Doing so enables the middleware and toolbar only in the local environment +> — and not in production, where you likely do not want to expose such +> information! + +## php-middleware/php-debug-bar + +[php-middleware/php-debug-bar](https://github.com/php-middleware/phpdebugbar) +provides a PSR-7 middleware wrapper around [maximebf/php-debugbar](https://github.com/maximebf/php-debugbar), +a popular framework-agnostic debug bar for PHP projects. + +First, install the middleware in your application: + +```bash +$ composer require php-middleware/php-debug-bar +``` + +This package supplies a config provider, which could be added to your +`config/config.php` when using zend-config-aggregator or +expressive-config-manager. However, because it should only be enabled in +development, we recommend creating a "local" configuration file (e.g., +`config/autoload/php-debugbar.local.php`) when you need to enable it, with the +following contents: + +```php + ### Use locally! +> +> Remember to enable `PhpMiddleware\PhpDebugBar\ConfigProvider` only in your +> development enviroments! diff --git a/docs/book/v3/cookbook/flash-messengers.md b/docs/book/v3/cookbook/flash-messengers.md new file mode 100644 index 00000000..e32d785a --- /dev/null +++ b/docs/book/v3/cookbook/flash-messengers.md @@ -0,0 +1,267 @@ +# How Can I Implement Flash Messages? + +*Flash messages* are used to display one-time messages to a user. A typical use +case is for setting and later displaying a successful submission via a +[Post/Redirect/Get (PRG)](https://en.wikipedia.org/wiki/Post/Redirect/Get) +workflow, where the flash message would be set during the POST request, but +displayed during the GET request. (PRG is used to prevent double-submission of +forms.) As such, flash messages usually are session-based; the message is set in +one request, and accessed and cleared in another. + +Expressive does not provide native session facilities out-of-the-box, which +means you will need: + +- Session functionality. +- Flash message functionality, for handling message expiry from the session + after first access. + +A number of flash message libraries already exist that can be integrated via +middleware, and these typically either use PHP's ext/session functionality or +have a dependency on a session library. Two such libraries are slim/flash and +damess/expressive-session-middleware. + +## slim/flash + +Slim's [Flash messages service provider](https://github.com/slimphp/Slim-Flash) can be +used in Expressive. It uses PHP's native session support. + +First, you'll need to add it to your application: + +```bash +$ composer require slim/flash +``` + +Second, create middleware that will add the flash message provider to the request: + +```php +handle( + $request->withAttribute('flash', new Messages()) + ); + } +} +``` + +Third, we will register the new middleware with our container as an invokable. +Edit either the file `config/autoload/dependencies.global.php` or +`config/autoload/middleware-pipeline.global.php` to add the following: + +```php +return [ + 'dependencies' => [ + 'invokables' => [ + App\SlimFlashMiddleware::class => App\SlimFlashMiddleware::class, + /* ... */ + ], + /* ... */ + ], +]; +``` + +Finally, let's register it with our middleware pipeline. For programmatic +pipelines, pipe the middleware somewhere, generally before the routing middleware: + +```php +$app->pipe(App\SlimFlashMiddleware::class); +``` + +Or as part of a routed middleware pipeline: + +```php +$app->post('/form/handler', [ + App\SlimFlashMiddleware::class, + FormHandlerMiddleware::class, +]); +``` + +If using configuration-driven pipelines, edit +`config/autoload/middleware-pipeline.global.php` to make the following +additions: + +```php +return [ + 'middleware_pipeline' => [ + 'always' => [ + 'middleware' => [ + 'App\SlimFlashMiddleware', + /* ... */ + ], + 'priority' => 10000, + ], + /* ... */ + ], +]; +``` + +> ### Where to register the flash middleware +> +> Sessions can sometimes be expensive. As such, you may not want the flash +> middleware enabled for every request. If this is the case, add the flash +> middleware as part of a route-specific pipeline instead, as demonstrated +> in the programmatic pipelines above. + +From here, you can add and read messages by accessing the request's flash +attribute. As an example, middleware generating messages might read as follows: + +```php +use Psr\Http\Server\RequestHandlerInterface; +use Zend\Diactoros\Response\RedirectResponse; + +function($request, RequestHandlerInterface $handler) +{ + $flash = $request->getAttribute('flash'); + $flash->addMessage('message', 'Hello World!'); + + return new RedirectResponse('/other-middleware'); +} +``` + +And middleware consuming the message might read: + +```php +use Psr\Http\Server\RequestHandlerInterface; + +function($request, RequestHandlerInterface $handler) +{ + $flash = $request->getAttribute('flash'); + $messages = $flash->getMessages(); + // ... +} +``` + +From there, it's a matter of providing the flash messages to your template. + +## damess/expressive-session-middleware and Aura.Session + +[damess/expressive-session-middleware](https://github.com/dannym87/expressive-session-middleware) +provides middleware for initializing an +[Aura.Session](https://github.com/auraphp/Aura.Session) instance; Aura.Session +provides flash messaging capabilities as part of its featureset. + +Install it via Composer: + +```bash +$ composer require damess/expressive-session-middleware +``` + +In `config/autoload/dependencies.global.php`, add an entry for Aura.Session: + +```php +return [ + 'dependencies' => [ + 'factories' => [ + Aura\Session\Session::class => DaMess\Factory\AuraSessionFactory::class, + /* ... */ + ], + /* ... */ + ], +]; +``` + +In either `config/autoload/dependencies.global.php` or +`config/autoload/middleware-pipeline.global.php`, add a factory entry for the +`damess/expressive-session-middleware`: + +```php +return [ + 'dependencies' => [ + 'factories' => [ + DaMess\Http\SessionMiddleware::class => DaMess\Factory\SessionMiddlewareFactory::class, + /* ... */ + ], + /* ... */ + ], +]; +``` + +Finally, add it to your middleware pipeline. For programmatic pipelines: + +```php +use DaMess\Http\SessionMiddleware; + +$app->pipe(SessionMiddleware::class); +/* ... */ +``` + +If using configuration-driven pipelines, edit `config/autoload/middleware-pipeline.global.php` +and add an entry for the new middleware: + +```php +return [ + 'middleware_pipeline' => [ + 'always' => [ + 'middleware' => [ + DaMess\Http\SessionMiddleware::class, + /* ... */ + ], + 'priority' => 10000, + ], + /* ... */ + ], +]; +``` + +> ### Where to register the session middleware +> +> Sessions can sometimes be expensive. As such, you may not want the session +> middleware enabled for every request. If this is the case, add the session +> middleware as part of a route-specific pipeline instead. + +Once enabled, the `SessionMiddleware` will inject the Aura.Session instance into +the request as the `session` attribute; you can thus retrieve it within +middleware using the following: + +```php +$session = $request->getAttribute('session'); +``` + +To create and consume flash messages, use Aura.Session's +[flash values](https://github.com/auraphp/Aura.Session#flash-values). As +an example, the middleware that is processing a POST request might set a flash +message: + +```php +use Psr\Http\Server\RequestHandlerInterface; +use Zend\Diactoros\Response\RedirectResponse; + +function($request, RequestHandlerInterface $handler) +{ + $session = $request->getAttribute('session'); + $session->getSegment(__NAMESPACE__) + ->setFlash('message', 'Hello World!'); + + return new RedirectResponse('/other-middleware'); +} +``` + +Another middleware, to which the original middleware redirects, might look like +this: + +```php +use Psr\Http\Server\RequestHandlerInterface; + +function($request, RequestHandlerInterface $handler) +{ + $session = $request->getAttribute('session'); + $message = $session->getSegment(__NAMESPACE__) + ->getFlash('message'); + // ... +} +``` + +From there, it's a matter of providing the flash messages to your template. diff --git a/docs/book/v3/cookbook/passing-data-between-middleware.md b/docs/book/v3/cookbook/passing-data-between-middleware.md new file mode 100644 index 00000000..a1460565 --- /dev/null +++ b/docs/book/v3/cookbook/passing-data-between-middleware.md @@ -0,0 +1,120 @@ +# Passing Data Between Middleware + +A frequently asked question is how to pass data between middleware. + +The answer is present in every middleware: via request object attributes. + +Middleware is always executed in the order in which it is piped to the +application. This way you can ensure the request object in middleware receiving +data contains an attribute containing data passed by outer middleware. + +In the following example, `PassingDataMiddleware` prepares data to pass as a +request attribute to nested middleware. We use the fully qualified class name +for the attribute name to ensure uniqueness, but you can name it anything you +want. + +```php +namespace App\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class PassingDataMiddleware implements MiddlewareInterface +{ + // ... + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + // Step 1: Do something first + $data = [ + 'foo' => 'bar', + ]; + + // Step 2: Inject data into the request, call the next middleware and wait for the response + + // Expressive 3.X: + $response = $handler->handle($request->withAttribute(self::class, $data)); + + // Expressive 2.X: + $response = $handler->process($request->withAttribute(self::class, $data)); + + // Step 3: Optionally, do something (with the response) before returning the response + + // Step 4: Return the response + return $response; + } +} +``` + +Later, `ReceivingDataMiddleware` grabs the data and processes it: + +```php +namespace App\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class ReceivingDataMiddleware implements MiddlewareInterface +{ + // ... + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + // Step 1: Grab the data from the request and use it + $data = $request->getAttribute(PassingDataMiddleware::class); + + // Step 2: Call the next middleware and wait for the response + + // Expressive 3.X: + $response = $handler->handle($request); + + // Expressive 2.X: + $response = $handler->process($request); + + // Step 3: Optionally, do something (with the response) before returning the response + + // Step 4: Return the response + return $response; + } +} +``` + +Of course, you could also use the data in routed middleware, which is usually at +the innermost layer of your application. The `ExampleAction` below takes that +information and passes it to the template renderer to create an `HtmlResponse`: + +```php +namespace App\Action; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Zend\Diactoros\Response\HtmlResponse; + +class ExampleAction implements MiddlewareInterface +{ + // ... + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + // Step 1: Grab the data from the request + $data = $request->getAttribute(PassingDataMiddleware::class); + $id = $request->getAttribute('id'); + + // Step 2: Do some more stuff + + // Step 3: Return a Response + return new HtmlResponse( + $this->templateRenderer->render('blog::entry', [ + 'data' => $data, + 'id' => $id, + ]) + ); + } +} +``` diff --git a/docs/book/v3/cookbook/route-specific-pipeline.md b/docs/book/v3/cookbook/route-specific-pipeline.md new file mode 100644 index 00000000..706b5b25 --- /dev/null +++ b/docs/book/v3/cookbook/route-specific-pipeline.md @@ -0,0 +1,200 @@ +# How can I specify a route-specific middleware pipeline? + +Sometimes you may want to use a middleware pipeline only if a particular route +is matched. As an example, for an API resource, you might want to: + +- check for authentication credentials +- check for authorization for the selected action +- parse the incoming body +- validate the parsed body parameters + +*before* you actually execute the selected middleware. The above might each be +encapsulated as discrete middleware, but should be executed within the routed +middleware's context. + +You can accomplish this in one of two ways: + +- Have your middleware service resolve to a `MiddlewarePipe` instance that + composes the various middlewares. +- Specify an array of middlewares (either as actual instances, or as container + service names); this effectively creates and returns a `MiddlewarePipe`. + +## Resolving to a MiddlewarePipe + +You can do this programmatically within a container factory, assuming you are +using a container that supports factories. + +```php +use Psr\Container\ContainerInterface; +use Zend\Stratigility\MiddlewarePipe; + +class ApiResourcePipelineFactory +{ + public function __invoke(ContainerInterface $container) + { + $pipeline = new MiddlewarePipe(); + + // These correspond to the bullet points above + $pipeline->pipe($container->get('AuthenticationMiddleware')); + $pipeline->pipe($container->get('AuthorizationMiddleware')); + $pipeline->pipe($container->get('BodyParsingMiddleware')); + $pipeline->pipe($container->get('ValidationMiddleware')); + + // This is the actual middleware you're routing to. + $pipeline->pipe($container->get('ApiResource')); + + return $pipeline; + } +} +``` + +This gives you full control over the creation of the pipeline. You would, +however, need to ensure that you map the middleware to the pipeline factory when +setting up your container configuration. + +One alternative when using zend-servicemanager is to use a [delegator factory](https://docs.zendframework.com/zend-servicemanager/delegators/). +Delegator factories allow you to decorate the primary factory used to create the +middleware in order to change the instance or return an alternate instance. In +this case, we'd do the latter. The following is an example: + +```php +use Psr\Container\ContainerInterface; +use Zend\ServiceManager\DelegatorFactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; +use Zend\Stratigility\MiddlewarePipe; + +class ApiResourcePipelineDelegatorFactory implements DelegatorFactoryInterface +{ + /** + * zend-servicemanager v3 support + */ + public function __invoke( + ContainerInterface $container, + $name, + callable $callback, + array $options = null + ) { + $pipeline = new MiddlewarePipe(); + + // These correspond to the bullet points above + $pipeline->pipe($container->get('AuthenticationMiddleware')); + $pipeline->pipe($container->get('AuthorizationMiddleware')); + $pipeline->pipe($container->get('BodyParsingMiddleware')); + $pipeline->pipe($container->get('ValidationMiddleware')); + + // This is the actual middleware you're routing to. + $pipeline->pipe($callback()); + + return $pipeline; + } + + /** + * zend-servicemanager v2 support + */ + public function createDelegatorWithName( + ServiceLocatorInterface $container, + $name, + $requestedName, + $callback + ) { + return $this($container, $name, $callback); + } +} +``` + +When configuring the container, you'd do something like the following: + +```php +return [ + 'dependencies' => [ + 'factories' => [ + 'AuthenticationMiddleware' => '...', + 'AuthorizationMiddleware' => '...', + 'BodyParsingMiddleware' => '...', + 'ValidationMiddleware' => '...', + 'ApiResourceMiddleware' => '...', + ], + 'delegators' => [ + 'ApiResourceMiddleware' => [ + 'ApiResourcePipelineDelegatorFactory', + ], + ], + ], +]; +``` + +This approach allows you to cleanly separate the factory for your middleware +from the pipeline you want to compose it in, and allows you to re-use the +pipeline creation across multiple middleware if desired. + +## Middleware Arrays + +If you'd rather not create a factory for each such middleware, the other option +is to use arrays of middlewares in your configuration or when routing manually. + +Via configuration looks like this: + +```php +return [ + 'routes' => [ + [ + 'name' => 'api-resource', + 'path' => '/api/resource[/{id:[a-f0-9]{32}}]', + 'allowed_methods' => ['GET', 'POST', 'PATCH', 'DELETE'], + 'middleware' => [ + 'AuthenticationMiddleware', + 'AuthorizationMiddleware', + 'BodyParsingMiddleware', + 'ValidationMiddleware', + 'ApiResourceMiddleware', + ], + ], + ], +]; +``` + +Manual routing looks like this: + +```php +$app->route('/api/resource[/{id:[a-f0-9]{32}}]', [ + 'AuthenticationMiddleware', + 'AuthorizationMiddleware', + 'BodyParsingMiddleware', + 'ValidationMiddleware', + 'ApiResourceMiddleware', +], ['GET', 'POST', 'PATCH', 'DELETE'], 'api-resource'); +``` + +When either of these approaches are used, the individual middleware listed +**MUST** be one of the following: + +- an instance of `Interop\Http\ServerMiddleware\MiddlewareInterface`; +- a callable middleware (will be decorated as interop middleware); +- a service name of middleware available in the container; +- a fully qualified class name of a directly instantiable (no constructor + arguments) middleware class. + +This approach is essentially equivalent to creating a factory that returns a +middleware pipeline. + +## What about pipeline middleware configuration? + +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' => [ + 'api' => [ + 'path' => '/api', + 'middleware' => [ + 'AuthenticationMiddleware', + 'AuthorizationMiddleware', + 'BodyParsingMiddleware', + 'ValidationMiddleware', + ], + 'priority' => 100, + ], + ], +]; +``` diff --git a/docs/book/v3/cookbook/setting-locale-depending-routing-parameter.md b/docs/book/v3/cookbook/setting-locale-depending-routing-parameter.md new file mode 100644 index 00000000..8e020ec0 --- /dev/null +++ b/docs/book/v3/cookbook/setting-locale-depending-routing-parameter.md @@ -0,0 +1,222 @@ +# How can I setup the locale depending on a routing parameter? + +Localized web applications often set the locale (and therefor the language) +based on a routing parameter, the session, or a specialized sub-domain. +In this recipe we will concentrate on using a routing parameter. + +> ### Routing parameters +> +> 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 +> tedious quickly. In such a case, you may want to look at the related recipe +> on [setting the locale without routing parameters](setting-locale-without-routing-parameter.md). + +## Setting up the route + +If you want to set the locale depending on an routing parameter, you first have +to add a locale parameter to each route that requires localization. + +In the following examples, we use the `locale` parameter, which should consist +of two lowercase alphabetical characters. + +### Dependency configuration + +The examples assume the following middleware dependency configuration: + +```php +use Application\Action; + +return [ + 'dependencies' => [ + 'factories' => [ + Action\HomePageAction::class => Action\HomePageFactory::class, + Action\ContactPageAction::class => Action\ContactPageFactory::class, + ], + ], +]; +``` + +### Programmatic routes + +The following describes routing configuration for use when using a +programmatic application. + +```php +use Application\Action\ContactPageAction; +use Application\Action\HomePageAction; + +$localeOptions = ['locale' => '[a-z]{2,3}([-_][a-zA-Z]{2}|)']; + +$app->get('/:locale', HomePageAction::class, 'home') + ->setOptions($localeOptions); +$app->get('/:locale/contact', ContactPageAction::class, 'contact') + ->setOptions($localeOptions); +``` + +### Configuration-based routes + +The following describes routing configuration for use when using a +configuration-driven application. + +```php +return [ + 'routes' => [ + [ + 'name' => 'home', + 'path' => '/:locale', + 'middleware' => Application\Action\HomePageAction::class, + 'allowed_methods' => ['GET'], + 'options' => [ + 'constraints' => [ + 'locale' => '[a-z]{2,3}([-_][a-zA-Z]{2}|)', + ], + ], + ], + [ + 'name' => 'contact', + 'path' => '/:locale/contact', + 'middleware' => Application\Action\ContactPageAction::class, + 'allowed_methods' => ['GET'], + 'options' => [ + 'constraints' => [ + 'locale' => '[a-z]{2,3}([-_][a-zA-Z]{2}|)', + ], + ], + ], + ], +]; +``` +> ### Note: Routing may differ based on router +> +> The routing examples in this recipe use syntax for the zend-mvc router, and, +> as such, may not work in your application. +> +> For Aura.Router, the 'home' route as listed above would read: +> +> ```php +> [ +> 'name' => 'home', +> 'path' => '/{locale}', +> 'middleware' => Application\Action\HomePageAction::class, +> 'allowed_methods' => ['GET'], +> 'options' => [ +> 'constraints' => [ +> 'tokens' => [ +> 'locale' => '[a-z]{2,3}([-_][a-zA-Z]{2}|)', +> ], +> ], +> ], +> ] +> ``` +> +> For FastRoute: +> +> ```php +> [ +> 'name' => 'home', +> 'path' => '/{locale:[a-z]{2,3}([-_][a-zA-Z]{2}|)}', +> 'middleware' => Application\Action\HomePageAction::class, +> 'allowed_methods' => ['GET'], +> ] +> ``` +> +> As such, be aware as you read the examples that you might not be able to +> simply cut-and-paste them without modification. + + +## 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 localization middleware that acts on the route result, and +registered in the pipeline immediately following the routing middleware. + +Such a `LocalizationMiddleware` class could look similar to this: + +```php +getAttribute( + 'locale', + Locale::acceptFromHttp( + $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] ?? 'en_US' + ) + ); + + // Store the locale as a request attribute + return $handler->handle($request->withAttribute(self::LOCALIZATION_ATTRIBUTE, $locale)); + } +} +``` + +> ### Locale::setDefault is unsafe +> +> Do not use `Locale::setDefault($locale)` to set a global static locale. +> PSR-7 apps may run in async processes, which could lead to another process +> overwriting the value, and thus lead to unexpected results for your users. +> +> Use a request parameter as detailed above instead, as the request is created +> specific to each process. + +Register this new middleware in either `config/autoload/middleware-pipeline.global.php` +or `config/autoload/dependencies.global.php`: + +```php +return [ + 'dependencies' => [ + 'invokables' => [ + LocalizationMiddleware::class => LocalizationMiddleware::class, + /* ... */ + ], + /* ... */ + ], +]; +``` + +If using a programmatic pipeline, pipe it immediately after your routing middleware: + +```php +use Application\I18n\LocalizationMiddleware; + +/* ... */ +$app->pipeRoutingMiddleware(); +$app->pipe(LocalizationMiddleware::class); +/* ... */ +``` + +If using a configuration-driven application, register it within your +`config/autoload/middleware-pipeline.global.php` file, injecting it +into the pipeline following the routing middleware: + +```php +return [ + 'middleware_pipeline' => [ + /* ... */ + [ + 'middleware' => [ + Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, + Helper\UrlHelperMiddleware::class, + LocalizationMiddleware::class, + Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, + ], + 'priority' => 1, + ], + /* ... */ + ], +]; +``` diff --git a/docs/book/v3/cookbook/setting-locale-without-routing-parameter.md b/docs/book/v3/cookbook/setting-locale-without-routing-parameter.md new file mode 100644 index 00000000..35d237b0 --- /dev/null +++ b/docs/book/v3/cookbook/setting-locale-without-routing-parameter.md @@ -0,0 +1,228 @@ +# How can I setup the locale without routing parameters? + +Localized web applications often set the locale (and therefore the language) +based on a routing parameter, the session, or a specialized sub-domain. +In this recipe we will concentrate on introspecting the URI path via middleware, +which allows you to have a global mechanism for detecting the locale without +requiring any changes to existing routes. + +> ## Distinguishing between routes that require localization +> +> If your application has a mixture of routes that require localization, and +> those that do not, the solution in this recipe may lead to multiple URIs +> that resolve to the identical action, which may be undesirable. In such +> cases, you may want to prefix the specific routes that require localization +> 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 locale from the URI + +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 value to setup the locale. It also: + +- amends the request with a truncated path (removing the locale segment). +- adds the locale segment as the base path of the `UrlHelper`. + +```php +helper = $helper; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + $uri = $request->getUri(); + + $path = $uri->getPath(); + + if (! preg_match('#^/(?P
- * 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);
- * };
- *
+ * If two arguments are present, they are passed to pipe(), after first
+ * passing the second argument to the factory's prepare() method.
*
- * This is done to delay fetching the middleware until it is actually used;
- * the upshot is that you will not be notified if the service is invalid to
- * use as middleware until runtime.
+ * If only one argument is presented, it is passed to the factory prepare()
+ * method.
*
- * Middleware may also be passed as an array; each item in the array must
- * resolve to middleware eventually (i.e., callable or service name).
+ * The resulting middleware, in both cases, is piped to the pipeline.
*
- * Finally, ensures that the route middleware is only ever registered
- * once.
- *
- * @param string|array|callable $path Either a URI path prefix, or middleware.
- * @param null|string|array|callable $middleware Middleware
- * @return self
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middlewareOrPath
+ * Either the middleware to pipe, or the path to segregate the $middleware
+ * by, via a PathMiddlewareDecorator.
+ * @param null|string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * If present, middleware or request handler to segregate by the path
+ * specified in $middlewareOrPath.
*/
- public function pipe($path, $middleware = null)
+ public function pipe($middlewareOrPath, $middleware = null) : void
{
- if (null === $middleware) {
- $middleware = $this->prepareMiddleware(
- $path,
- $this->router,
- $this->responsePrototype,
- $this->container
- );
- $path = '/';
- }
-
- if (! is_callable($middleware)
- && (is_string($middleware) || is_array($middleware))
- ) {
- $middleware = $this->prepareMiddleware(
- $middleware,
- $this->router,
- $this->responsePrototype,
- $this->container
- );
- }
-
- if ($middleware instanceof Router\Middleware\RouteMiddleware && $this->routeMiddlewareIsRegistered) {
- return $this;
- }
+ $middleware = $middleware ?: $middlewareOrPath;
+ $path = $middleware === $middlewareOrPath ? '/' : $middlewareOrPath;
- if ($middleware instanceof Router\Middleware\DispatchMiddleware && $this->dispatchMiddlewareIsRegistered) {
- return $this;
- }
+ $middleware = $path !== '/'
+ ? path($path, $this->factory->prepare($middleware))
+ : $this->factory->prepare($middleware);
- if (! in_array($path, ['', '/'], true)) {
- $middleware = path($path, $middleware);
- }
-
- parent::pipe($middleware);
-
- if ($middleware instanceof Router\Middleware\RouteMiddleware) {
- $this->routeMiddlewareIsRegistered = true;
- }
-
- if ($middleware instanceof Router\Middleware\DispatchMiddleware) {
- $this->dispatchMiddlewareIsRegistered = true;
- }
-
- return $this;
- }
-
- /**
- * Register the routing middleware in the middleware pipeline.
- *
- * @deprecated since 2.2.0; to be removed in 3.0.0. Use pipe() with routing
- * middleware or a service name resolving to routing middleware instead.
- * @return void
- */
- public function pipeRoutingMiddleware()
- {
- if ($this->routeMiddlewareIsRegistered) {
- return;
- }
- $this->pipe(self::ROUTING_MIDDLEWARE);
- }
-
- /**
- * Register the dispatch middleware in the middleware pipeline.
- *
- * @deprecated since 2.2.0; to be removed in 3.0.0. Use pipe() with dispatch
- * middleware or a service name resolving to dispatch middleware instead.
- * @return void
- */
- public function pipeDispatchMiddleware()
- {
- if ($this->dispatchMiddlewareIsRegistered) {
- return;
- }
- $this->pipe(self::DISPATCH_MIDDLEWARE);
+ $this->pipeline->pipe($middleware);
}
/**
* Add a route for the route middleware to match.
*
- * Accepts either a Router\Route instance, or a combination of a path and
- * middleware, and optionally the HTTP methods allowed.
- *
- * On first invocation, pipes the route middleware to the middleware
- * pipeline.
- *
- * @param string|Router\Route $path
- * @param callable|string|array $middleware Middleware (or middleware service name) to associate with route.
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
* @param null|array $methods HTTP method to accept; null indicates any.
* @param null|string $name The name of the route.
- * @return Router\Route
- * @throws Exception\InvalidArgumentException if $path is not a Router\Route AND middleware is null.
*/
- public function route($path, $middleware = null, array $methods = null, $name = null)
+ public function route(string $path, $middleware, array $methods = null, string $name = null) : Router\Route
{
- if (! $path instanceof Router\Route && null === $middleware) {
- throw new Exception\InvalidArgumentException(sprintf(
- '%s expects either a route argument, or a combination of a path and middleware arguments',
- __METHOD__
- ));
- }
-
- if ($path instanceof Router\Route) {
- $route = $path;
- $path = $route->getPath();
- $methods = $route->getAllowedMethods();
- $name = $route->getName();
- }
-
- $this->checkForDuplicateRoute($path, $methods);
-
- if (! isset($route)) {
- $methods = null === $methods ? Router\Route::HTTP_METHOD_ANY : $methods;
- $middleware = $this->prepareMiddleware(
- $middleware,
- $this->router,
- $this->responsePrototype,
- $this->container
- );
- $route = new Router\Route($path, $middleware, $methods, $name);
- }
-
- $this->routes[] = $route;
- $this->router->addRoute($route);
-
- return $route;
+ return $this->routes->route(
+ $path,
+ $this->factory->prepare($middleware),
+ $methods,
+ $name
+ );
}
/**
- * Retrieve all directly registered routes with the application.
- *
- * @return Router\Route[]
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- public function getRoutes()
+ public function get(string $path, $middleware, string $name = null) : Router\Route
{
- return $this->routes;
+ return $this->route($path, $middleware, ['GET'], $name);
}
/**
- * Run the application
- *
- * If no request or response are provided, the method will use
- * ServerRequestFactory::fromGlobals to create a request instance, and
- * instantiate a default response instance.
- *
- * It retrieves the default delegate using getDefaultDelegate(), and
- * uses that to process itself.
- *
- * Once it has processed itself, it emits the returned response using the
- * composed emitter.
- *
- * @param null|ServerRequestInterface $request
- * @param null|ResponseInterface $response
- * @return void
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
+ public function post(string $path, $middleware, $name = null) : Router\Route
{
- try {
- $request = $request ?: ServerRequestFactory::fromGlobals();
- } catch (InvalidArgumentException $e) {
- // Unable to parse uploaded files
- $this->emitMarshalServerRequestException($e);
- return;
- } catch (UnexpectedValueException $e) {
- // Invalid request method
- $this->emitMarshalServerRequestException($e);
- return;
- }
-
- $response = $response ?: new Response();
- $request = $request->withAttribute('originalResponse', $response);
- $delegate = $this->getDefaultDelegate();
-
- $response = $this->process($request, $delegate);
-
- $emitter = $this->getEmitter();
- $emitter->emit($response);
+ return $this->route($path, $middleware, ['POST'], $name);
}
/**
- * Retrieve the IoC container.
- *
- * If no IoC container is registered, we raise an exception.
- *
- * @deprecated since 2.2.0; to be removed in 3.0.0. This feature is
- * replaced by Zend\Expressive\MiddlewareFactory in that release, which
- * can be retrieved as a service from the application container.
- * @return ContainerInterface
- * @throws Exception\ContainerNotRegisteredException
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- public function getContainer()
+ public function put(string $path, $middleware, string $name = null) : Router\Route
{
- if (null === $this->container) {
- throw new Exception\ContainerNotRegisteredException();
- }
- return $this->container;
+ return $this->route($path, $middleware, ['PUT'], $name);
}
/**
- * Return the default delegate to use during `run()` if the stack is exhausted.
- *
- * If no default delegate is present, attempts the following:
- *
- * - If a container is composed, and it has the 'Zend\Expressive\Delegate\DefaultDelegate'
- * service, pulls that service, assigns it, and returns it.
- * - If no container is composed, creates an instance of Delegate\NotFoundDelegate
- * using the current response prototype only (i.e., no templating).
- *
- * @deprecated since 2.2.0; to be removed in 3.0.0. This feature has no
- * equivalent in that version.
- * @return DelegateInterface
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- public function getDefaultDelegate()
+ public function patch(string $path, $middleware, string $name = null) : Router\Route
{
- if ($this->defaultDelegate) {
- return $this->defaultDelegate;
- }
-
- if ($this->container && $this->container->has('Zend\Expressive\Delegate\DefaultDelegate')) {
- $this->defaultDelegate = $this->container->get('Zend\Expressive\Delegate\DefaultDelegate');
- return $this->defaultDelegate;
- }
-
- if ($this->container) {
- $factory = new Container\NotFoundDelegateFactory();
- $this->defaultDelegate = $factory($this->container);
- return $this->defaultDelegate;
- }
-
- $this->defaultDelegate = new Delegate\NotFoundDelegate($this->responsePrototype);
- return $this->defaultDelegate;
+ return $this->route($path, $middleware, ['PATCH'], $name);
}
/**
- * Retrieve an emitter to use during run().
- *
- * If none was registered during instantiation, this will lazy-load an
- * EmitterStack composing an SapiEmitter instance.
- *
- * @deprecated since 2.2.0; to be removed in 3.0.0. This feature has no
- * equivalent in that version; the responsibility has been moved to a
- * new collaborator.
- * @return EmitterInterface
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- public function getEmitter()
+ public function delete(string $path, $middleware, string $name = null) : Router\Route
{
- if (! $this->emitter) {
- $this->emitter = new Emitter\EmitterStack();
- $this->emitter->push(new SapiEmitter());
- }
- return $this->emitter;
+ return $this->route($path, $middleware, ['DELETE'], $name);
}
/**
- * Determine if the route is duplicated in the current list.
- *
- * Checks if a route with the same name or path exists already in the list;
- * if so, and it responds to any of the $methods indicated, raises
- * a DuplicateRouteException indicating a duplicate route.
- *
- * @param string $path
- * @param null|array $methods
- * @throws Exception\DuplicateRouteException on duplicate route detection.
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * Middleware or request handler (or service name resolving to one of
+ * those types) to associate with route.
+ * @param null|string $name The name of the route.
*/
- private function checkForDuplicateRoute($path, $methods = null)
+ public function any(string $path, $middleware, string $name = null) : Router\Route
{
- if (null === $methods) {
- $methods = Router\Route::HTTP_METHOD_ANY;
- }
-
- $matches = array_filter($this->routes, function (Router\Route $route) use ($path, $methods) {
- if ($path !== $route->getPath()) {
- return false;
- }
-
- if ($methods === Router\Route::HTTP_METHOD_ANY) {
- return true;
- }
-
- return array_reduce($methods, function ($carry, $method) use ($route) {
- return ($carry || $route->allowsMethod($method));
- }, false);
- });
-
- if (! empty($matches)) {
- throw new Exception\DuplicateRouteException(sprintf(
- 'Duplicate route detected; same name or path ("%s"),'
- . ' and one or more HTTP methods intersect (%s)',
- $path,
- is_array($methods) ? implode(', ', $methods) : '*'
- ));
- }
+ return $this->route($path, $middleware, null, $name);
}
/**
- * @param \Exception|\Throwable $exception
- * @return void
+ * Retrieve all directly registered routes with the application.
+ *
+ * @return Router\Route[]
*/
- private function emitMarshalServerRequestException($exception)
+ public function getRoutes() : array
{
- if ($this->container && $this->container->has(Middleware\ErrorResponseGenerator::class)) {
- $generator = $this->container->get(Middleware\ErrorResponseGenerator::class);
- $response = $generator($exception, new ServerRequest(), $this->responsePrototype);
- } else {
- $response = $this->responsePrototype
- ->withStatus(StatusCode::STATUS_BAD_REQUEST);
- }
-
- $emitter = $this->getEmitter();
- $emitter->emit($response);
+ return $this->routes->getRoutes();
}
}
diff --git a/src/ApplicationConfigInjectionTrait.php b/src/ApplicationConfigInjectionTrait.php
deleted file mode 100644
index 7be1b540..00000000
--- a/src/ApplicationConfigInjectionTrait.php
+++ /dev/null
@@ -1,61 +0,0 @@
-container || ! $this->container->has('config'))
- ) {
- return;
- }
-
- ApplicationConfigInjectionDelegator::injectPipelineFromConfig(
- $this,
- is_array($config) ? $config : $this->container->get('config')
- );
- }
-
- /**
- * Inject routes from configuration.
- *
- * Proxies to ApplicationConfigInjectionDelegator::injectRoutesFromConfig
- *
- * @param null|array $config If null, attempts to pull the 'config' service
- * from the composed container.
- * @return void
- * @throws Exception\InvalidArgumentException
- */
- public function injectRoutesFromConfig(array $config = null)
- {
- if (! is_array($config)
- && (! $this->container || ! $this->container->has('config'))
- ) {
- return;
- }
-
- ApplicationConfigInjectionDelegator::injectRoutesFromConfig(
- $this,
- is_array($config) ? $config : $this->container->get('config')
- );
- }
-}
diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php
index 015b9daa..829a684d 100644
--- a/src/ConfigProvider.php
+++ b/src/ConfigProvider.php
@@ -1,14 +1,19 @@
$this->getDependencies(),
];
}
- /**
- * @return array
- */
- public function getDependencies()
+ public function getDependencies() : array
{
// @codingStandardsIgnoreStart
return [
'aliases' => [
- Delegate\NotFoundDelegate::class => Handler\NotFoundHandler::class,
- Middleware\DispatchMiddleware::class => Router\Middleware\DispatchMiddleware::class,
- Middleware\ImplicitHeadMiddleware::class => Router\Middleware\ImplicitHeadMiddleware::class,
- Middleware\ImplicitOptionsMiddleware::class => Router\Middleware\ImplicitOptionsMiddleware::class,
- Middleware\RouteMiddleware::class => Router\Middleware\RouteMiddleware::class,
- 'Zend\Expressive\Delegate\DefaultDelegate' => Handler\NotFoundHandler::class,
- ],
- 'invokables' => [
- Router\Middleware\DispatchMiddleware::class => Router\Middleware\DispatchMiddleware::class,
+ DEFAULT_DELEGATE => Handler\NotFoundHandler::class,
+ DISPATCH_MIDDLEWARE => Router\Middleware\DispatchMiddleware::class,
+ IMPLICIT_HEAD_MIDDLEWARE => Router\Middleware\ImplicitHeadMiddleware::class,
+ IMPLICIT_OPTIONS_MIDDLEWARE => Router\Middleware\ImplicitOptionsMiddleware::class,
+ NOT_FOUND_MIDDLEWARE => Handler\NotFoundHandler::class,
+ ROUTE_MIDDLEWARE => Router\Middleware\PathBasedRoutingMiddleware::class,
],
'factories' => [
Application::class => Container\ApplicationFactory::class,
+ ApplicationPipeline::class => Container\ApplicationPipelineFactory::class,
+ EmitterInterface::class => Container\EmitterFactory::class,
ErrorHandler::class => Container\ErrorHandlerFactory::class,
- Handler\NotFoundHandler::class => Container\NotFoundDelegateFactory::class,
+ Handler\NotFoundHandler::class => Container\NotFoundHandlerFactory::class,
+ MiddlewareContainer::class => Container\MiddlewareContainerFactory::class,
+ MiddlewareFactory::class => Container\MiddlewareFactoryFactory::class,
// Change the following in development to the WhoopsErrorResponseGeneratorFactory:
Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class,
- Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class,
+ RequestHandlerRunner::class => Container\RequestHandlerRunnerFactory::class,
ResponseInterface::class => Container\ResponseFactoryFactory::class,
+ Response\ServerRequestErrorResponseGenerator::class => Container\ServerRequestErrorResponseGeneratorFactory::class,
+ ServerRequestInterface::class => Container\ServerRequestFactoryFactory::class,
StreamInterface::class => Container\StreamFactoryFactory::class,
-
- // These are duplicates, in case the zend-expressive-router package ConfigProvider is not wired:
- Router\Middleware\ImplicitHeadMiddleware::class => Router\Middleware\ImplicitHeadMiddlewareFactory::class,
- Router\Middleware\ImplicitOptionsMiddleware::class => Router\Middleware\ImplicitOptionsMiddlewareFactory::class,
- Router\Middleware\RouteMiddleware::class => Router\Middleware\RouteMiddlewareFactory::class,
],
];
// @codingStandardsIgnoreEnd
diff --git a/src/Container/ApplicationConfigInjectionDelegator.php b/src/Container/ApplicationConfigInjectionDelegator.php
index e90b1685..ec30e06c 100644
--- a/src/Container/ApplicationConfigInjectionDelegator.php
+++ b/src/Container/ApplicationConfigInjectionDelegator.php
@@ -1,17 +1,22 @@
[
* // An array of middleware to register with the pipeline.
* // entries to register prior to routing/dispatching...
- * // - entry for \Zend\Expressive\Router\Middleware\RouteMiddleware::class
+ * // - entry for \Zend\Expressive\Router\Middleware\PathBasedRoutingMiddleware::class
+ * // - entry for \Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware::class
* // - entry for \Zend\Expressive\Router\Middleware\DispatchMiddleware::class
* // entries to register after routing/dispatching...
* ],
@@ -102,10 +106,8 @@ public function __invoke(ContainerInterface $container, $serviceName, callable $
* the `middleware` value of a specification. Internally, this will create
* a `Zend\Stratigility\MiddlewarePipe` instance, with the middleware
* specified piped in the order provided.
- *
- * @return void
*/
- public static function injectPipelineFromConfig(Application $application, array $config)
+ public static function injectPipelineFromConfig(Application $application, array $config) : void
{
if (empty($config['middleware_pipeline'])) {
return;
@@ -119,7 +121,7 @@ public static function injectPipelineFromConfig(Application $application, array
);
foreach ($queue as $spec) {
- $path = isset($spec['path']) ? $spec['path'] : '/';
+ $path = $spec['path'] ?? '/';
$application->pipe($path, $spec['middleware']);
}
}
@@ -127,43 +129,11 @@ public static function injectPipelineFromConfig(Application $application, array
/**
* Inject routes from configuration.
*
- * Introspects the provided configuration for routes to inject in the
- * application instance.
- *
- * The following configuration structure can be used to define routes:
- *
- *
- * return [
- * 'routes' => [
- * [
- * 'path' => '/path/to/match',
- * 'middleware' => 'Middleware Service Name or Callable',
- * 'allowed_methods' => ['GET', 'POST', 'PATCH'],
- * 'options' => [
- * 'stuff' => 'to',
- * 'pass' => 'to',
- * 'the' => 'underlying router',
- * ],
- * ],
- * // etc.
- * ],
- * ];
- *
- *
- * Each route MUST have a path and middleware key at the minimum.
- *
- * The "allowed_methods" key may be omitted, can be either an array or the
- * value of the Zend\Expressive\Router\Route::HTTP_METHOD_ANY constant; any
- * valid HTTP method token is allowed, which means you can specify custom HTTP
- * methods as well.
- *
- * The "options" key may also be omitted, and its interpretation will be
- * dependent on the underlying router used.
+ * Proxies to ApplicationConfigInjectionDelegator::injectRoutesFromConfig
*
- * @return void
* @throws InvalidArgumentException
*/
- public static function injectRoutesFromConfig(Application $application, array $config)
+ public static function injectRoutesFromConfig(Application $application, array $config) : void
{
if (! isset($config['routes']) || ! is_array($config['routes'])) {
return;
@@ -185,7 +155,7 @@ public static function injectRoutesFromConfig(Application $application, array $c
}
}
- $name = isset($spec['name']) ? $spec['name'] : null;
+ $name = $spec['name'] ?? null;
$route = $application->route(
$spec['path'],
$spec['middleware'],
@@ -219,21 +189,11 @@ public static function injectRoutesFromConfig(Application $application, array $c
* If the 'middleware' value is missing, or not viable as middleware, it
* raises an exception, to ensure the pipeline is built correctly.
*
- * @return callable
* @throws InvalidArgumentException
*/
- private static function createCollectionMapper()
+ private static function createCollectionMapper() : callable
{
- $appMiddleware = [
- Application::ROUTING_MIDDLEWARE,
- Application::DISPATCH_MIDDLEWARE,
- ];
-
- return function ($item) use ($appMiddleware) {
- if (in_array($item, $appMiddleware, true)) {
- return ['middleware' => $item];
- }
-
+ return function ($item) {
if (! is_array($item) || ! array_key_exists('middleware', $item)) {
throw new InvalidArgumentException(sprintf(
'Invalid pipeline specification received; must be an array'
@@ -257,10 +217,8 @@ private static function createCollectionMapper()
*
* The function is useful to reduce an array of pipeline middleware to a
* priority queue.
- *
- * @return callable
*/
- private static function createPriorityQueueReducer()
+ private static function createPriorityQueueReducer() : callable
{
// $serial is used to ensure that items of the same priority are enqueued
// in the order in which they are inserted.
diff --git a/src/Container/ApplicationFactory.php b/src/Container/ApplicationFactory.php
index 1849b7b4..e9f21b6f 100644
--- a/src/Container/ApplicationFactory.php
+++ b/src/Container/ApplicationFactory.php
@@ -1,176 +1,42 @@
has('config') ? $container->get('config') : [];
- $config = $config instanceof ArrayObject ? $config->getArrayCopy() : $config;
-
- $router = $container->has(RouterInterface::class)
- ? $container->get(RouterInterface::class)
- : new FastRouteRouter();
-
- $delegate = $container->has('Zend\Expressive\Delegate\DefaultDelegate')
- ? $container->get('Zend\Expressive\Delegate\DefaultDelegate')
- : null;
-
- $emitter = $container->has(EmitterInterface::class)
- ? $container->get(EmitterInterface::class)
- : null;
-
- $app = new Application($router, $container, $delegate, $emitter);
-
- if (empty($config['zend-expressive']['programmatic_pipeline'])) {
- $this->injectRoutesAndPipeline($container, $router, $app, $config);
- }
-
- return $app;
- }
-
- /**
- * Injects routes and the middleware pipeline into the application.
- *
- * @return void
- */
- private function injectRoutesAndPipeline(
- ContainerInterface $container,
- RouterInterface $router,
- Application $app,
- array $config
- ) {
- if (empty($config['middleware_pipeline'])
- && (! isset($config['routes']) || ! is_array($config['routes']))
- ) {
- return;
- }
-
- if (empty($config['middleware_pipeline']) && isset($config['routes']) && is_array($config['routes'])) {
- $app->pipe($this->getRoutingMiddleware($container, $router, $app));
- $app->pipe($this->getDispatchMiddleware($container, $app));
- }
-
- ApplicationConfigInjectionDelegator::injectRoutesFromConfig($app, $config);
- ApplicationConfigInjectionDelegator::injectPipelineFromConfig($app, $config);
- }
-
- /**
- * Discovers or creates the route middleware.
- *
- * If the RouteMiddleware is present in the container, it returns the
- * service.
- *
- * Otherwise, it creates RouteMiddleware using the router being composed in
- * the application, along with a response prototype.
- *
- * @return MiddlewareInterface
- */
- private function getRoutingMiddleware(ContainerInterface $container, RouterInterface $router, Application $app)
- {
- if ($container->has(RouteMiddleware::class)) {
- return $container->get(RouteMiddleware::class);
- }
-
- return new RouteMiddleware(
- $router,
- $this->getResponsePrototype($container, $app)
+ return new Application(
+ $container->get(MiddlewareFactory::class),
+ $container->get(ApplicationPipeline::class),
+ $container->get(PathBasedRoutingMiddleware::class),
+ $container->get(RequestHandlerRunner::class)
);
}
-
- /**
- * Discover or create the dispatch middleware.
- *
- * If the DispatchMiddleware is present in the application's container, it
- * returns the service. Otherwise, instantiates and returns it directly.
- *
- * @return MiddlewareInterface
- */
- private function getDispatchMiddleware(ContainerInterface $container, Application $app)
- {
- return $container->has(DispatchMiddleware::class)
- ? $container->get(DispatchMiddleware::class)
- : new DispatchMiddleware();
- }
-
- /**
- * Get the response prototype.
- *
- * If not available in the container, uses reflection to pull it from the
- * application.
- *
- * If in the container, fetches it. If the value is callable, uses it as
- * a factory to generate and return the response.
- *
- * @return ResponseInterface
- */
- private function getResponsePrototype(ContainerInterface $container, Application $app)
- {
- if (! $container->has(ResponseInterface::class)) {
- $r = new ReflectionProperty($app, 'responsePrototype');
- $r->setAccessible(true);
- return $r->getValue($app);
- }
-
- $response = $container->get(ResponseInterface::class);
- return is_callable($response) ? $response() : $response;
- }
}
diff --git a/src/Container/ApplicationPipelineFactory.php b/src/Container/ApplicationPipelineFactory.php
new file mode 100644
index 00000000..5cd9772a
--- /dev/null
+++ b/src/Container/ApplicationPipelineFactory.php
@@ -0,0 +1,22 @@
+push(new SapiEmitter());
+ return $stack;
+ }
+}
diff --git a/src/Container/ErrorHandlerFactory.php b/src/Container/ErrorHandlerFactory.php
index bedfb94f..73b2470d 100644
--- a/src/Container/ErrorHandlerFactory.php
+++ b/src/Container/ErrorHandlerFactory.php
@@ -1,29 +1,27 @@
has(ErrorResponseGenerator::class)
? $container->get(ErrorResponseGenerator::class)
: null;
- return new ErrorHandler(new Response(), $generator);
+ return new ErrorHandler($container->get(ResponseInterface::class), $generator);
}
}
diff --git a/src/Container/ErrorResponseGeneratorFactory.php b/src/Container/ErrorResponseGeneratorFactory.php
index e3008724..8ac93e49 100644
--- a/src/Container/ErrorResponseGeneratorFactory.php
+++ b/src/Container/ErrorResponseGeneratorFactory.php
@@ -1,10 +1,12 @@
has('config') ? $container->get('config') : [];
- $debug = isset($config['debug']) ? $config['debug'] : false;
+ $debug = $config['debug'] ?? false;
- $template = isset($config['zend-expressive']['error_handler']['template_error'])
- ? $config['zend-expressive']['error_handler']['template_error']
- : ErrorResponseGenerator::TEMPLATE_DEFAULT;
+ $template = $config['zend-expressive']['error_handler']['template_error']
+ ?? ErrorResponseGenerator::TEMPLATE_DEFAULT;
$renderer = $container->has(TemplateRendererInterface::class)
? $container->get(TemplateRendererInterface::class)
diff --git a/src/Container/Exception/ExceptionInterface.php b/src/Container/Exception/ExceptionInterface.php
index 6a853c12..5527afc5 100644
--- a/src/Container/Exception/ExceptionInterface.php
+++ b/src/Container/Exception/ExceptionInterface.php
@@ -1,10 +1,12 @@
get(MiddlewareContainer::class)
+ );
+ }
+}
diff --git a/src/Container/NotFoundDelegateFactory.php b/src/Container/NotFoundDelegateFactory.php
deleted file mode 100644
index df0cc58f..00000000
--- a/src/Container/NotFoundDelegateFactory.php
+++ /dev/null
@@ -1,41 +0,0 @@
-has('config') ? $container->get('config') : [];
- $renderer = $container->has(TemplateRendererInterface::class)
- ? $container->get(TemplateRendererInterface::class)
- : null;
- $template = isset($config['zend-expressive']['error_handler']['template_404'])
- ? $config['zend-expressive']['error_handler']['template_404']
- : NotFoundDelegate::TEMPLATE_DEFAULT;
- $layout = isset($config['zend-expressive']['error_handler']['layout'])
- ? $config['zend-expressive']['error_handler']['layout']
- : NotFoundDelegate::LAYOUT_DEFAULT;
-
- return new NotFoundDelegate(new Response(), $renderer, $template, $layout);
- }
-}
diff --git a/src/Container/NotFoundHandlerFactory.php b/src/Container/NotFoundHandlerFactory.php
index b84b921c..83704d7f 100644
--- a/src/Container/NotFoundHandlerFactory.php
+++ b/src/Container/NotFoundHandlerFactory.php
@@ -1,24 +1,37 @@
get(NotFoundDelegate::class));
+ $config = $container->has('config') ? $container->get('config') : [];
+ $renderer = $container->has(TemplateRendererInterface::class)
+ ? $container->get(TemplateRendererInterface::class)
+ : null;
+ $template = $config['zend-expressive']['error_handler']['template_404']
+ ?? NotFoundHandler::TEMPLATE_DEFAULT;
+ $layout = $config['zend-expressive']['error_handler']['layout']
+ ?? NotFoundHandler::LAYOUT_DEFAULT;
+
+ return new NotFoundHandler(
+ $container->get(ResponseInterface::class),
+ $renderer,
+ $template,
+ $layout
+ );
}
}
diff --git a/src/Container/RequestHandlerRunnerFactory.php b/src/Container/RequestHandlerRunnerFactory.php
new file mode 100644
index 00000000..32c3819c
--- /dev/null
+++ b/src/Container/RequestHandlerRunnerFactory.php
@@ -0,0 +1,47 @@
+get(ApplicationPipeline::class),
+ $container->get(EmitterInterface::class),
+ $container->get(ServerRequestInterface::class),
+ $container->get(ServerRequestErrorResponseGenerator::class)
+ );
+ }
+}
diff --git a/src/Container/ResponseFactoryFactory.php b/src/Container/ResponseFactoryFactory.php
index f9ef4e8a..1fa30db0 100644
--- a/src/Container/ResponseFactoryFactory.php
+++ b/src/Container/ResponseFactoryFactory.php
@@ -1,10 +1,12 @@
has('config') ? $container->get('config') : [];
+ $debug = $config['debug'] ?? false;
+
+ $renderer = $container->has(TemplateRendererInterface::class)
+ ? $container->get(TemplateRendererInterface::class)
+ : null;
+
+ $template = $config['zend-expressive']['error_handler']['template_error']
+ ?? ServerRequestErrorResponseGenerator::TEMPLATE_DEFAULT;
+
+ return new ServerRequestErrorResponseGenerator(
+ $container->get(ResponseInterface::class),
+ $debug,
+ $renderer,
+ $template
+ );
+ }
+}
diff --git a/src/Container/ServerRequestFactoryFactory.php b/src/Container/ServerRequestFactoryFactory.php
new file mode 100644
index 00000000..46b04356
--- /dev/null
+++ b/src/Container/ServerRequestFactoryFactory.php
@@ -0,0 +1,45 @@
+get('Zend\Expressive\Whoops')
diff --git a/src/Container/WhoopsFactory.php b/src/Container/WhoopsFactory.php
index 8ac66336..b79cdc98 100644
--- a/src/Container/WhoopsFactory.php
+++ b/src/Container/WhoopsFactory.php
@@ -1,10 +1,12 @@
has('config') ? $container->get('config') : [];
- $config = isset($config['whoops']) ? $config['whoops'] : [];
+ $config = $config['whoops'] ?? [];
$whoops = new Whoops();
$whoops->writeToOutput(false);
@@ -67,7 +66,7 @@ public function __invoke(ContainerInterface $container)
* @param array|\ArrayAccess $config
* @return void
*/
- private function registerJsonHandler(Whoops $whoops, $config)
+ private function registerJsonHandler(Whoops $whoops, $config) : void
{
if (empty($config['json_exceptions']['display'])) {
return;
diff --git a/src/Container/WhoopsPageHandlerFactory.php b/src/Container/WhoopsPageHandlerFactory.php
index f962c833..79140ce7 100644
--- a/src/Container/WhoopsPageHandlerFactory.php
+++ b/src/Container/WhoopsPageHandlerFactory.php
@@ -1,10 +1,12 @@
has('config') ? $container->get('config') : [];
- $config = isset($config['whoops']) ? $config['whoops'] : [];
+ $config = $config['whoops'] ?? [];
$pageHandler = new PrettyPageHandler();
@@ -51,13 +49,10 @@ public function __invoke(ContainerInterface $container)
* Inject an editor into the whoops configuration.
*
* @see https://github.com/filp/whoops/blob/master/docs/Open%20Files%20In%20An%20Editor.md
- * @param PrettyPageHandler $handler
* @param array|\ArrayAccess $config
- * @param ContainerInterface $container
- * @return void
* @throws Exception\InvalidServiceException for an invalid editor definition.
*/
- private function injectEditor(PrettyPageHandler $handler, $config, ContainerInterface $container)
+ private function injectEditor(PrettyPageHandler $handler, $config, ContainerInterface $container) : void
{
if (! isset($config['editor'])) {
return;
diff --git a/src/Delegate/NotFoundDelegate.php b/src/Delegate/NotFoundDelegate.php
deleted file mode 100644
index 1b1cbf33..00000000
--- a/src/Delegate/NotFoundDelegate.php
+++ /dev/null
@@ -1,18 +0,0 @@
-emit($response)) {
- return null;
- }
- }
-
- return false;
- }
-
- /**
- * Set an emitter on the stack by index.
- *
- * @param mixed $index
- * @param EmitterInterface $emitter
- * @return void
- * @throws InvalidArgumentException if not an EmitterInterface instance
- */
- public function offsetSet($index, $emitter)
- {
- $this->validateEmitter($emitter);
- parent::offsetSet($index, $emitter);
- }
-
- /**
- * Push an emitter to the stack.
- *
- * @param EmitterInterface $emitter
- * @return void
- * @throws InvalidArgumentException if not an EmitterInterface instance
- */
- public function push($emitter)
- {
- $this->validateEmitter($emitter);
- parent::push($emitter);
- }
-
- /**
- * Unshift an emitter to the stack.
- *
- * @param EmitterInterface $emitter
- * @return void
- * @throws InvalidArgumentException if not an EmitterInterface instance
- */
- public function unshift($emitter)
- {
- $this->validateEmitter($emitter);
- parent::unshift($emitter);
- }
-
- /**
- * Validate that an emitter implements EmitterInterface.
- *
- * @param mixed $emitter
- * @return void
- * @throws InvalidArgumentException for non-emitter instances
- */
- private function validateEmitter($emitter)
- {
- if (! $emitter instanceof EmitterInterface) {
- throw new Exception\InvalidArgumentException(sprintf(
- '%s expects an EmitterInterface implementation',
- __CLASS__
- ));
- }
- }
-}
diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php
index 66f5fadf..fa26caa1 100644
--- a/src/Exception/BadMethodCallException.php
+++ b/src/Exception/BadMethodCallException.php
@@ -1,10 +1,12 @@
responsePrototype = $responsePrototype;
+ // Factory cast to closure in order to provide return type safety.
+ $this->responseFactory = function () use ($responseFactory) : ResponseInterface {
+ return $responseFactory();
+ };
$this->renderer = $renderer;
$this->template = $template;
$this->layout = $layout;
@@ -59,33 +58,30 @@ public function __construct(
/**
* Creates and returns a 404 response.
*
- * @param ServerRequestInterface $request
- * @return ResponseInterface
+ * @param ServerRequestInterface $request Passed to internal handler
*/
- public function process(ServerRequestInterface $request)
+ public function handle(ServerRequestInterface $request) : ResponseInterface
{
- if (! $this->renderer) {
+ if ($this->renderer === null) {
return $this->generatePlainTextResponse($request);
}
- return $this->generateTemplatedResponse($request);
+ return $this->generateTemplatedResponse($this->renderer, $request);
}
/**
* Generates a plain text response indicating the request method and URI.
- *
- * @param ServerRequestInterface $request
- * @return ResponseInterface
*/
- private function generatePlainTextResponse(ServerRequestInterface $request)
+ private function generatePlainTextResponse(ServerRequestInterface $request) : ResponseInterface
{
- $response = $this->responsePrototype->withStatus(StatusCodeInterface::STATUS_NOT_FOUND);
+ $response = ($this->responseFactory)()->withStatus(StatusCodeInterface::STATUS_NOT_FOUND);
$response->getBody()
->write(sprintf(
'Cannot %s %s',
$request->getMethod(),
(string) $request->getUri()
));
+
return $response;
}
@@ -93,15 +89,15 @@ private function generatePlainTextResponse(ServerRequestInterface $request)
* Generates a response using a template.
*
* Template will receive the current request via the "request" variable.
- *
- * @param ServerRequestInterface $request
- * @return ResponseInterface
*/
- private function generateTemplatedResponse(ServerRequestInterface $request)
- {
- $response = $this->responsePrototype->withStatus(StatusCodeInterface::STATUS_NOT_FOUND);
+ private function generateTemplatedResponse(
+ TemplateRendererInterface $renderer,
+ ServerRequestInterface $request
+ ) : ResponseInterface {
+
+ $response = ($this->responseFactory)()->withStatus(StatusCodeInterface::STATUS_NOT_FOUND);
$response->getBody()->write(
- $this->renderer->render($this->template, ['request' => $request, 'layout' => $this->layout])
+ $renderer->render($this->template, ['request' => $request, 'layout' => $this->layout])
);
return $response;
diff --git a/src/IsCallableInteropMiddlewareTrait.php b/src/IsCallableInteropMiddlewareTrait.php
deleted file mode 100644
index a99a3e30..00000000
--- a/src/IsCallableInteropMiddlewareTrait.php
+++ /dev/null
@@ -1,90 +0,0 @@
-isCallable($middleware)) {
- return false;
- }
-
- $r = $this->reflectMiddleware($middleware);
- $paramsCount = $r->getNumberOfParameters();
-
- return $paramsCount === 2;
- }
-
- /**
- * Reflect a callable middleware.
- *
- * Duplicates MiddlewarePipe::getReflectionFunction, but that method is not
- * callable due to private visibility.
- *
- * @param callable $middleware
- * @return \ReflectionFunctionAbstract
- */
- private function reflectMiddleware(callable $middleware)
- {
- if (is_array($middleware)) {
- $class = array_shift($middleware);
- $method = array_shift($middleware);
- return new ReflectionMethod($class, $method);
- }
-
- if ($middleware instanceof Closure || ! is_object($middleware)) {
- return new ReflectionFunction($middleware);
- }
-
- return new ReflectionMethod($middleware, '__invoke');
- }
-}
diff --git a/src/MarshalMiddlewareTrait.php b/src/MarshalMiddlewareTrait.php
deleted file mode 100644
index 8b3fd1b1..00000000
--- a/src/MarshalMiddlewareTrait.php
+++ /dev/null
@@ -1,230 +0,0 @@
-triggerLegacyMiddlewareDeprecation($middleware);
- return $container && $container->has(RouteMiddleware::class)
- ? $container->get(RouteMiddleware::class)
- : new RouteMiddleware($router, $responsePrototype);
- }
-
- if ($middleware === Application::DISPATCH_MIDDLEWARE) {
- $this->triggerLegacyMiddlewareDeprecation($middleware);
- return $container && $container->has(DispatchMiddleware::class)
- ? $container->get(DispatchMiddleware::class)
- : new DispatchMiddleware();
- }
-
- if ($middleware instanceof MiddlewareInterface) {
- return $middleware;
- }
-
- if ($this->isCallableInteropMiddleware($middleware)) {
- return middleware($middleware);
- }
-
- if ($this->isCallable($middleware)) {
- $this->triggerDoublePassMiddlewareDeprecation($middleware);
- return doublePassMiddleware($middleware, $responsePrototype);
- }
-
- if (is_array($middleware)) {
- return $this->marshalMiddlewarePipe($middleware, $router, $responsePrototype, $container);
- }
-
- if (is_string($middleware) && $container && $container->has($middleware)) {
- return new Middleware\LazyLoadingMiddleware($container, $responsePrototype, $middleware);
- }
-
- if (is_string($middleware)) {
- return $this->marshalInvokableMiddleware($middleware, $responsePrototype);
- }
-
- throw new Exception\InvalidMiddlewareException(sprintf(
- 'Unable to resolve middleware "%s" to a callable or MiddlewareInterface implementation',
- is_object($middleware) ? get_class($middleware) . '[Object]' : gettype($middleware) . '[Scalar]'
- ));
- }
-
- /**
- * 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 Router\RouterInterface $router
- * @param ResponseInterface $responsePrototype
- * @param null|ContainerInterface $container
- * @return MiddlewarePipe
- * @throws Exception\InvalidMiddlewareException for any invalid middleware items.
- */
- private function marshalMiddlewarePipe(
- array $middlewares,
- Router\RouterInterface $router,
- ResponseInterface $responsePrototype,
- ContainerInterface $container = null
- ) {
- $middlewarePipe = new MiddlewarePipe();
- $middlewarePipe->setResponsePrototype($responsePrototype);
-
- foreach ($middlewares as $middleware) {
- $middlewarePipe->pipe(
- $this->prepareMiddleware($middleware, $router, $responsePrototype, $container)
- );
- }
-
- return $middlewarePipe;
- }
-
- /**
- * Attempt to instantiate the given middleware.
- *
- * @param string $middleware
- * @param ResponseInterface $responsePrototype
- * @return ServerMiddlewareInterface
- * @throws Exception\InvalidMiddlewareException if $middleware is not a class.
- * @throws Exception\InvalidMiddlewareException if $middleware does not resolve
- * to either an invokable class or ServerMiddlewareInterface instance.
- */
- private function marshalInvokableMiddleware($middleware, ResponseInterface $responsePrototype)
- {
- if (! class_exists($middleware)) {
- throw new Exception\InvalidMiddlewareException(sprintf(
- 'Unable to create middleware "%s"; not a valid class or service name',
- $middleware
- ));
- }
-
- $instance = new $middleware();
-
- if ($instance instanceof MiddlewareInterface) {
- return $instance;
- }
-
- if ($this->isCallableInteropMiddleware($instance)) {
- return middleware($instance);
- }
-
- if (! is_callable($instance)) {
- throw new Exception\InvalidMiddlewareException(sprintf(
- 'Middleware of class "%s" is invalid; neither invokable nor a MiddlewareInterface instance',
- $middleware
- ));
- }
-
- $this->triggerDoublePassMiddlewareDeprecation($instance);
- return doublePassMiddleware($instance, $responsePrototype);
- }
-
- /**
- * @param string $middlewareType
- * @return void
- */
- private function triggerLegacyMiddlewareDeprecation($middlewareType)
- {
- switch ($middlewareType) {
- case (Application::ROUTING_MIDDLEWARE):
- $constant = sprintf('%s::ROUTING_MIDDLEWARE', Application::class);
- $type = 'routing';
- $useInstead = RouteMiddleware::class;
- break;
- case (Application::DISPATCH_MIDDLEWARE):
- $constant = sprintf('%s::DISPATCH_MIDDLEWARE', Application::class);
- $type = 'dispatch';
- $useInstead = DispatchMiddleware::class;
- break;
- }
-
- trigger_error(sprintf(
- 'Usage of the %s constant for specifying %s middleware is deprecated;'
- . ' pipe() the middleware directly, or reference it by its service name "%s"',
- $constant,
- $type,
- $useInstead
- ), E_USER_DEPRECATED);
- }
-
- /**
- * @param callable $middleware
- * @return void
- */
- private function triggerDoublePassMiddlewareDeprecation(callable $middleware)
- {
- if (is_object($middleware)) {
- $type = get_class($middleware);
- } elseif (is_string($middleware)) {
- $type = 'callable:' . $middleware;
- } else {
- $type = 'callable';
- }
-
- trigger_error(sprintf(
- 'Detected double-pass middleware (%s).'
- . ' Usage of callable double-pass middleware is deprecated. Before piping or routing'
- . ' such middleware, pass it to Zend\Stratigility\doublePassMiddleware(), along with'
- . ' a PSR-7 response instance.',
- $type
- ), E_USER_DEPRECATED);
- }
-}
diff --git a/src/Middleware/DispatchMiddleware.php b/src/Middleware/DispatchMiddleware.php
deleted file mode 100644
index 7ca87eb6..00000000
--- a/src/Middleware/DispatchMiddleware.php
+++ /dev/null
@@ -1,30 +0,0 @@
-debug = (bool) $isDevelopmentMode;
+ $this->debug = $isDevelopmentMode;
$this->renderer = $renderer;
$this->template = $template;
}
- /**
- * @param \Throwable|\Exception $e
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @return ResponseInterface
- */
- public function __invoke($e, ServerRequestInterface $request, ResponseInterface $response)
- {
+ public function __invoke(
+ Throwable $e,
+ ServerRequestInterface $request,
+ ResponseInterface $response
+ ) : ResponseInterface {
$response = $response->withStatus(Utils::getStatusCode($e, $response));
if ($this->renderer) {
- return $this->prepareTemplatedResponse($e, $request, $response);
- }
-
- return $this->prepareDefaultResponse($e, $response);
- }
-
- /**
- * @param \Throwable|\Exception $e
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @return ResponseInterface
- */
- private function prepareTemplatedResponse($e, ServerRequestInterface $request, ResponseInterface $response)
- {
- $templateData = [
- 'response' => $response,
- 'request' => $request,
- 'uri' => (string) $request->getUri(),
- 'status' => $response->getStatusCode(),
- 'reason' => $response->getReasonPhrase(),
- ];
-
- if ($this->debug) {
- $templateData['error'] = $e;
- }
-
- $response->getBody()->write(
- $this->renderer->render($this->template, $templateData)
- );
-
- return $response;
- }
-
- /**
- * @param \Throwable|\Exception $e
- * @param ResponseInterface $response
- * @return ResponseInterface
- */
- private function prepareDefaultResponse($e, ResponseInterface $response)
- {
- $message = 'An unexpected error occurred';
-
- if ($this->debug) {
- $message .= "; strack trace:\n\n" . $this->prepareStackTrace($e);
- }
-
- $response->getBody()->write($message);
-
- return $response;
- }
-
- /**
- * Prepares a stack trace to display.
- *
- * @param \Throwable|\Exception $e
- * @return string
- */
- private function prepareStackTrace($e)
- {
- $message = '';
- do {
- $message .= sprintf(
- $this->stackTraceTemplate,
- get_class($e),
- $e->getFile(),
- $e->getLine(),
- $e->getMessage(),
- $e->getTraceAsString()
+ return $this->prepareTemplatedResponse(
+ $e,
+ $this->renderer,
+ [
+ 'response' => $response,
+ 'request' => $request,
+ 'uri' => (string) $request->getUri(),
+ 'status' => $response->getStatusCode(),
+ 'reason' => $response->getReasonPhrase(),
+ ],
+ $this->debug,
+ $response
);
- } while ($e = $e->getPrevious());
+ }
- return $message;
+ return $this->prepareDefaultResponse($e, $this->debug, $response);
}
}
diff --git a/src/Middleware/ImplicitHeadMiddleware.php b/src/Middleware/ImplicitHeadMiddleware.php
deleted file mode 100644
index cbe61fc7..00000000
--- a/src/Middleware/ImplicitHeadMiddleware.php
+++ /dev/null
@@ -1,48 +0,0 @@
-container = $container;
- $this->responsePrototype = $responsePrototype;
$this->middlewareName = $middlewareName;
}
/**
- * @param ServerRequestInterface $request
- * @param DelegateInterface $delegate
- * @return ResponseInterface
* @throws InvalidMiddlewareException for invalid middleware types pulled
* from the container.
*/
- public function process(ServerRequestInterface $request, DelegateInterface $delegate)
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$middleware = $this->container->get($this->middlewareName);
-
- // http-interop middleware
- if ($middleware instanceof ServerMiddlewareInterface) {
- return $middleware->process($request, $delegate);
- }
-
- // Unknown - invalid!
- if (! is_callable($middleware)) {
- throw new InvalidMiddlewareException(sprintf(
- 'Lazy-loaded middleware "%s" is neither invokable nor implements %s',
- $this->middlewareName,
- ServerMiddlewareInterface::class
- ));
- }
-
- // Callable http-interop middleware
- if ($this->isCallableInteropMiddleware($middleware)) {
- return $middleware($request, $delegate);
- }
-
- // Legacy double-pass signature
- return $middleware($request, $this->responsePrototype, function ($request, $response) use ($delegate) {
- return $delegate->process($request);
- });
+ return $middleware->process($request, $handler);
}
}
diff --git a/src/Middleware/NotFoundHandler.php b/src/Middleware/NotFoundHandler.php
deleted file mode 100644
index 7b7f36f9..00000000
--- a/src/Middleware/NotFoundHandler.php
+++ /dev/null
@@ -1,47 +0,0 @@
-internalDelegate = $internalDelegate;
- }
-
- /**
- * Creates and returns a 404 response.
- *
- * @param ServerRequestInterface $request Passed to internal delegate
- * @param DelegateInterface $delegate Ignored.
- * @return ResponseInterface
- */
- public function process(ServerRequestInterface $request, DelegateInterface $delegate)
- {
- return $this->internalDelegate->process($request);
- }
-}
diff --git a/src/Middleware/RouteMiddleware.php b/src/Middleware/RouteMiddleware.php
deleted file mode 100644
index 9f854013..00000000
--- a/src/Middleware/RouteMiddleware.php
+++ /dev/null
@@ -1,31 +0,0 @@
-whoops = $whoops;
}
- /**
- * @param \Throwable|\Exception $e
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @return ResponseInterface
- */
- public function __invoke($e, ServerRequestInterface $request, ResponseInterface $response)
- {
+ public function __invoke(
+ Throwable $e,
+ ServerRequestInterface $request,
+ ResponseInterface $response
+ ) : ResponseInterface {
// Walk through all handlers
foreach ($this->whoops->getHandlers() as $handler) {
// Add fancy data for the PrettyPageHandler
@@ -82,18 +82,14 @@ public function __invoke($e, ServerRequestInterface $request, ResponseInterface
/**
* Prepare the Whoops page handler with a table displaying request information
- *
- * @param ServerRequestInterface $request
- * @param PrettyPageHandler $handler
- * @return void
*/
- private function prepareWhoopsHandler(ServerRequestInterface $request, PrettyPageHandler $handler)
+ private function prepareWhoopsHandler(ServerRequestInterface $request, PrettyPageHandler $handler) : void
{
$uri = $request->getAttribute('originalUri', false) ?: $request->getUri();
$request = $request->getAttribute('originalRequest', false) ?: $request;
$serverParams = $request->getServerParams();
- $scriptName = isset($serverParams['SCRIPT_NAME']) ? $serverParams['SCRIPT_NAME'] : '';
+ $scriptName = $serverParams['SCRIPT_NAME'] ?? '';
$handler->addDataTable('Expressive Application Request', [
'HTTP Method' => $request->getMethod(),
diff --git a/src/MiddlewareContainer.php b/src/MiddlewareContainer.php
new file mode 100644
index 00000000..646e140f
--- /dev/null
+++ b/src/MiddlewareContainer.php
@@ -0,0 +1,74 @@
+container = $container;
+ }
+
+ /**
+ * Returns true if the service is in the container, or resolves to an
+ * autoloadable class name.
+ *
+ * @param string $service
+ */
+ public function has($service) : bool
+ {
+ if ($this->container->has($service)) {
+ return true;
+ }
+
+ return class_exists($service);
+ }
+
+ /**
+ * Returns middleware pulled from container, or directly instantiated if
+ * not managed by the container.
+ *
+ * @param string $service
+ * @throws Exception\MissingDependencyException if the service does not
+ * exist, or is not a valid class name.
+ * @throws Exception\InvalidMiddlewareException if the service is not
+ * an instance of MiddlewareInterface.
+ */
+ public function get($service) : MiddlewareInterface
+ {
+ if (! $this->has($service)) {
+ throw Exception\MissingDependencyException::forMiddlewareService($service);
+ }
+
+ $middleware = $this->container->has($service)
+ ? $this->container->get($service)
+ : new $service();
+
+ $middleware = $middleware instanceof RequestHandlerInterface
+ ? new RequestHandlerMiddleware($middleware)
+ : $middleware;
+
+ if (! $middleware instanceof MiddlewareInterface) {
+ throw Exception\InvalidMiddlewareException::forMiddlewareService($service, $middleware);
+ }
+
+ return $middleware;
+ }
+}
diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php
new file mode 100644
index 00000000..b6671d0d
--- /dev/null
+++ b/src/MiddlewareFactory.php
@@ -0,0 +1,139 @@
+container = $container;
+ }
+
+ /**
+ * @param string|array|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * @throws Exception\InvalidMiddlewareException if argument is not one of
+ * the specified types.
+ */
+ public function prepare($middleware) : MiddlewareInterface
+ {
+ if ($middleware instanceof MiddlewareInterface) {
+ return $middleware;
+ }
+
+ if ($middleware instanceof RequestHandlerInterface) {
+ return new RequestHandlerMiddleware($middleware);
+ }
+
+ if (is_callable($middleware)) {
+ return $this->callable($middleware);
+ }
+
+ if (is_array($middleware)) {
+ return $this->pipeline(...$middleware);
+ }
+
+ if (! is_string($middleware) || $middleware === '') {
+ throw Exception\InvalidMiddlewareException::forMiddleware($middleware);
+ }
+
+ return $this->lazy($middleware);
+ }
+
+ /**
+ * Decorate callable standards-signature middleware via a CallableMiddlewareDecorator.
+ */
+ public function callable(callable $middleware) : CallableMiddlewareDecorator
+ {
+ return new CallableMiddlewareDecorator($middleware);
+ }
+
+ /**
+ * Decorate a RequestHandlerInterface as middleware via RequestHandlerMiddleware.
+ */
+ public function handler(RequestHandlerInterface $handler) : RequestHandlerMiddleware
+ {
+ return new RequestHandlerMiddleware($handler);
+ }
+
+ /**
+ * Create lazy loading middleware based on a service name.
+ */
+ public function lazy(string $middleware) : Middleware\LazyLoadingMiddleware
+ {
+ return new Middleware\LazyLoadingMiddleware($this->container, $middleware);
+ }
+
+ /**
+ * Create a middleware pipeline from an array of middleware.
+ *
+ * This method allows passing an array of middleware as either:
+ *
+ * - discrete arguments
+ * - an array of middleware, using the splat operator: pipeline(...$array)
+ * - an array of middleware as the sole argument: pipeline($array)
+ *
+ * Each item is passed to prepare() before being passed to the
+ * MiddlewarePipe instance the method returns.
+ *
+ * @param string|array|MiddlewarePipe $middleware
+ */
+ public function pipeline(...$middleware) : MiddlewarePipe
+ {
+ // Allow passing arrays of middleware or individual lists of middleware
+ if (is_array($middleware[0])
+ && count($middleware) === 1
+ ) {
+ $middleware = array_shift($middleware);
+ }
+
+ $pipeline = new MiddlewarePipe();
+ foreach ($middleware as $m) {
+ $pipeline->pipe($this->prepare($m));
+ }
+ return $pipeline;
+ }
+}
diff --git a/src/Response/ErrorResponseGeneratorTrait.php b/src/Response/ErrorResponseGeneratorTrait.php
new file mode 100644
index 00000000..c53223d2
--- /dev/null
+++ b/src/Response/ErrorResponseGeneratorTrait.php
@@ -0,0 +1,100 @@
+getBody()
+ ->write($renderer->render($this->template, $templateData));
+
+ return $response;
+ }
+
+ private function prepareDefaultResponse(
+ Throwable $e,
+ bool $debug,
+ ResponseInterface $response
+ ) : ResponseInterface {
+ $message = 'An unexpected error occurred';
+
+ if ($debug) {
+ $message .= "; stack trace:\n\n" . $this->prepareStackTrace($e);
+ }
+
+ $response->getBody()->write($message);
+
+ return $response;
+ }
+
+ /**
+ * Prepares a stack trace to display.
+ */
+ private function prepareStackTrace(Throwable $e) : string
+ {
+ $message = '';
+ do {
+ $message .= sprintf(
+ $this->stackTraceTemplate,
+ get_class($e),
+ $e->getFile(),
+ $e->getLine(),
+ $e->getMessage(),
+ $e->getTraceAsString()
+ );
+ } while ($e = $e->getPrevious());
+
+ return $message;
+ }
+}
diff --git a/src/Response/ServerRequestErrorResponseGenerator.php b/src/Response/ServerRequestErrorResponseGenerator.php
new file mode 100644
index 00000000..5c81ab13
--- /dev/null
+++ b/src/Response/ServerRequestErrorResponseGenerator.php
@@ -0,0 +1,69 @@
+responseFactory = function () use ($responseFactory) : ResponseInterface {
+ return $responseFactory();
+ };
+
+ $this->debug = $isDevelopmentMode;
+ $this->renderer = $renderer;
+ $this->template = $template;
+ }
+
+ public function __invoke(Throwable $e) : ResponseInterface
+ {
+ $response = ($this->responseFactory)();
+ $response = $response->withStatus(Utils::getStatusCode($e, $response));
+
+ if ($this->renderer) {
+ return $this->prepareTemplatedResponse(
+ $e,
+ $this->renderer,
+ [
+ 'response' => $response,
+ 'status' => $response->getStatusCode(),
+ 'reason' => $response->getReasonPhrase(),
+ ],
+ $this->debug,
+ $response
+ );
+ }
+
+ return $this->prepareDefaultResponse($e, $this->debug, $response);
+ }
+}
diff --git a/src/constants.php b/src/constants.php
new file mode 100644
index 00000000..10a5b605
--- /dev/null
+++ b/src/constants.php
@@ -0,0 +1,80 @@
+noopMiddleware = new TestAsset\InteropMiddleware();
- $this->router = $this->prophesize(RouterInterface::class);
- $this->disregardDeprecationNotices();
- }
-
- public function tearDown()
- {
- restore_error_handler();
- self::$existingClasses = null;
- }
-
- public function disregardDeprecationNotices()
- {
- set_error_handler(function ($errno, $errstr) {
- if (strstr($errstr, 'AppFactory is deprecated')) {
- return true;
- }
- return false;
- }, E_USER_DEPRECATED);
- }
-
-
- public function getRouterFromApplication(Application $app)
- {
- $r = new ReflectionProperty($app, 'router');
- $r->setAccessible(true);
- return $r->getValue($app);
- }
-
- public function testFactoryReturnsApplicationInstance()
- {
- $app = AppFactory::create();
- $this->assertInstanceOf(Application::class, $app);
- }
-
- public function testFactoryUsesFastRouteByDefault()
- {
- $app = AppFactory::create();
- $router = $this->getRouterFromApplication($app);
- $this->assertInstanceOf(FastRouteRouter::class, $router);
- }
-
- public function testFactoryUsesZf2ServiceManagerByDefault()
- {
- $app = AppFactory::create();
- $container = $app->getContainer();
- $this->assertInstanceOf(ServiceManager::class, $container);
- }
-
- public function testFactoryUsesEmitterStackWithSapiEmitterComposedByDefault()
- {
- $app = AppFactory::create();
- $emitter = $app->getEmitter();
- $this->assertInstanceOf(EmitterStack::class, $emitter);
-
- $this->assertCount(1, $emitter);
- $this->assertInstanceOf(SapiEmitter::class, $emitter->pop());
- }
-
- public function testFactoryAllowsPassingContainerToUse()
- {
- $container = $this->prophesize(ContainerInterface::class);
- $app = AppFactory::create($container->reveal());
- $test = $app->getContainer();
- $this->assertSame($container->reveal(), $test);
- }
-
- public function testFactoryAllowsPassingRouterToUse()
- {
- $router = $this->prophesize(RouterInterface::class);
- $app = AppFactory::create(null, $router->reveal());
- $test = $this->getRouterFromApplication($app);
- $this->assertSame($router->reveal(), $test);
- }
-
- /**
- * @see http://stackoverflow.com/questions/4753811/php-unit-tests-is-it-possible-to-test-for-a-fatal-error
- */
- public function testCannotInstantiateExternally()
- {
- $reflection = new ReflectionClass(AppFactory::class);
- $constructor = $reflection->getConstructor();
- $this->assertFalse($constructor->isPublic());
- }
-
- public function testThrowExceptionWhenContainerNotProvidedAndServiceManagerNotExists()
- {
- self::$existingClasses = [
- FastRouteRouter::class,
- ];
-
- $this->expectException(MissingDependencyException::class);
-
- AppFactory::create();
- }
-
- public function testThrowExceptionWhenContainerNotProvidedAndFastRouteRouterNotExists()
- {
- self::$existingClasses = [
- ServiceManager::class,
- ];
-
- $this->expectException(MissingDependencyException::class);
-
- AppFactory::create();
- }
-}
diff --git a/test/Application/ConfigInjectionTest.php b/test/Application/ConfigInjectionTest.php
deleted file mode 100644
index 702eb49d..00000000
--- a/test/Application/ConfigInjectionTest.php
+++ /dev/null
@@ -1,507 +0,0 @@
-container = $this->mockContainerInterface();
- $this->router = $this->prophesize(RouterInterface::class);
- $this->disregardDeprecationNotices();
- }
-
- public function tearDown()
- {
- restore_error_handler();
- }
-
- public function disregardDeprecationNotices()
- {
- set_error_handler(function ($errno, $errstr) {
- if (strstr($errstr, 'pipe() the middleware directly')) {
- return true;
- }
- if (strstr($errstr, 'doublePassMiddleware()')) {
- return true;
- }
- return false;
- }, E_USER_DEPRECATED);
- }
-
- public function createApplication()
- {
- return new Application($this->router->reveal(), $this->container->reveal());
- }
-
- public static function assertRoute($spec, array $routes)
- {
- Assert::assertThat(
- array_reduce($routes, function ($found, $route) use ($spec) {
- if ($found) {
- return $found;
- }
-
- if ($route->getPath() !== $spec['path']) {
- return false;
- }
-
- // We're just testing that middleware is present; since it may
- // be decorated, this might fail otherwise.
- if (! $route->getMiddleware()) {
- return false;
- }
-
- if (isset($spec['allowed_methods'])
- && $route->getAllowedMethods() !== $spec['allowed_methods']
- ) {
- return false;
- }
-
- if (! isset($spec['allowed_methods'])
- && $route->getAllowedMethods() !== Route::HTTP_METHOD_ANY
- ) {
- return false;
- }
-
- return true;
- }, false),
- Assert::isTrue(),
- 'Route created does not match any specifications'
- );
- }
-
- public static function assertPipelineContainsInstanceOf($class, $pipeline, $message = null)
- {
- $message = $message ?: 'Did not find expected middleware class type in pipeline';
- $found = false;
-
- foreach ($pipeline as $middleware) {
- if ($middleware instanceof $class) {
- $found = true;
- break;
- }
- }
-
- Assert::assertThat($found, Assert::isTrue(), $message);
- }
-
- public function injectableMiddleware()
- {
- return [
- [CallableInteropMiddleware::class],
- [
- function ($request, DelegateInterface $delegate) {
- },
- ],
- [[CallableInteropMiddleware::class, 'staticallyCallableMiddleware']],
- ];
- }
-
- /**
- * @dataProvider injectableMiddleware
- *
- * @param callable|array|string $middleware
- */
- public function testInjectRoutesFromConfigSetsUpRoutesFromConfig($middleware)
- {
- $pingMiddleware = $this->prophesize(MiddlewareInterface::class)->reveal();
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => $middleware,
- 'allowed_methods' => ['GET'],
- ],
- [
- 'path' => '/ping',
- 'middleware' => $pingMiddleware,
- 'allowed_methods' => ['GET'],
- ],
- ],
- ];
-
- $app = $this->createApplication();
-
- $app->injectRoutesFromConfig($config);
-
- $routes = $app->getRoutes();
-
- foreach ($config['routes'] as $route) {
- $this->assertRoute($route, $routes);
- }
- }
-
- public function testNoRoutesAreAddedIfSpecDoesNotProvidePathOrMiddleware()
- {
- $config = [
- 'routes' => [
- [
- 'allowed_methods' => ['GET'],
- ],
- [
- 'allowed_methods' => ['POST'],
- ],
- ],
- ];
-
- $app = $this->createApplication();
-
- $app->injectRoutesFromConfig($config);
-
- $routes = $app->getRoutes();
- $this->assertCount(0, $routes);
- }
-
- public function testPipelineContainingRoutingMiddlewareConstantPipesRoutingMiddleware()
- {
- $config = [
- 'middleware_pipeline' => [
- Application::ROUTING_MIDDLEWARE,
- ],
- ];
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $this->assertAttributeSame(true, 'routeMiddlewareIsRegistered', $app);
- }
-
- public function testPipelineContainingDispatchMiddlewareConstantPipesDispatchMiddleware()
- {
- $config = [
- 'middleware_pipeline' => [
- Application::DISPATCH_MIDDLEWARE,
- ],
- ];
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $this->assertAttributeSame(true, 'dispatchMiddlewareIsRegistered', $app);
- }
-
- public function testInjectPipelineFromConfigHonorsPriorityOrderWhenAttachingMiddleware()
- {
- $middleware = new TestAsset\InteropMiddleware();
-
- $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];
-
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $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()
- {
- $middleware = new TestAsset\InteropMiddleware();
-
- $pipeline1 = [['middleware' => clone $middleware]];
- $pipeline2 = [['middleware' => clone $middleware]];
- $pipeline3 = [['middleware' => clone $middleware]];
-
- $pipeline = array_merge($pipeline3, $pipeline1, $pipeline2);
- $config = ['middleware_pipeline' => $pipeline];
-
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $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()
- {
- $middleware = new TestAsset\InteropMiddleware();
-
- $pipeline = [
- ['middleware' => clone $middleware, 'priority' => -100],
- Application::ROUTING_MIDDLEWARE,
- ['middleware' => clone $middleware, 'priority' => 1],
- ['middleware' => clone $middleware],
- Application::DISPATCH_MIDDLEWARE,
- ['middleware' => clone $middleware, 'priority' => 100],
- ];
-
- $config = ['middleware_pipeline' => $pipeline];
-
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $test = $r->getValue($app);
-
- $this->assertSame($pipeline[5]['middleware'], $test->dequeue()->handler);
- $this->assertInstanceOf(RouteMiddleware::class, $test->dequeue()->handler);
- $this->assertSame($pipeline[2]['middleware'], $test->dequeue()->handler);
- $this->assertSame($pipeline[3]['middleware'], $test->dequeue()->handler);
- $this->assertInstanceOf(DispatchMiddleware::class, $test->dequeue()->handler);
- $this->assertSame($pipeline[0]['middleware'], $test->dequeue()->handler);
- }
-
- public function specMiddlewareContainingRoutingAndOrDispatchMiddleware()
- {
- // @codingStandardsIgnoreStart
- return [
- 'routing-only' => [[['middleware' => [Application::ROUTING_MIDDLEWARE]]]],
- 'dispatch-only' => [[['middleware' => [Application::DISPATCH_MIDDLEWARE]]]],
- 'both-routing-and-dispatch' => [[['middleware' => [Application::ROUTING_MIDDLEWARE, Application::DISPATCH_MIDDLEWARE]]]],
- ];
- // @codingStandardsIgnoreEnd
- }
-
- /**
- * @dataProvider specMiddlewareContainingRoutingAndOrDispatchMiddleware
- *
- * @param array $pipeline
- */
- public function testRoutingAndDispatchMiddlewareCanBeComposedWithinArrayStandardSpecification(array $pipeline)
- {
- $expected = $pipeline[0]['middleware'];
- $config = ['middleware_pipeline' => $pipeline];
-
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
-
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $appPipeline = $r->getValue($app);
-
- $this->assertCount(1, $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 Application::ROUTING_MIDDLEWARE:
- $middleware = RouteMiddleware::class;
- $message = 'Did not find routing middleware in pipeline';
- break;
- case Application::DISPATCH_MIDDLEWARE:
- $middleware = DispatchMiddleware::class;
- $message = 'Did not find dispatch middleware in pipeline';
- break;
- default:
- $this->fail('Unexpected value in pipeline passed from data provider');
- }
- $this->assertPipelineContainsInstanceOf($middleware, $innerPipeline, $message);
- }
- }
-
- public function testInjectPipelineFromConfigWithEmptyConfigAndNoConfigServiceDoesNothing()
- {
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig();
-
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
- $this->assertInstanceOf(SplQueue::class, $pipeline);
-
- $this->assertEquals(0, $pipeline->count());
- }
-
- public function testInjectRoutesFromConfigWithEmptyConfigAndNoConfigServiceDoesNothing()
- {
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $app->injectRoutesFromConfig();
- $this->assertAttributeEquals([], 'routes', $app);
- }
-
- public function testInjectRoutesFromConfigRaisesExceptionIfAllowedMethodsIsInvalid()
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => new TestAsset\InteropMiddleware(),
- 'allowed_methods' => 'not-valid',
- ],
- ],
- ];
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Allowed HTTP methods');
- $app->injectRoutesFromConfig($config);
- }
-
- public function testInjectRoutesFromConfigRaisesExceptionIfOptionsIsNotAnArray()
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => new TestAsset\InteropMiddleware(),
- 'allowed_methods' => ['GET'],
- 'options' => 'invalid',
- ],
- ],
- ];
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Route options must be an array');
- $app->injectRoutesFromConfig($config);
- }
-
- public function testInjectRoutesFromConfigCanProvideRouteOptions()
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => new TestAsset\InteropMiddleware(),
- 'allowed_methods' => ['GET'],
- 'options' => [
- 'foo' => 'bar',
- ],
- ],
- ],
- ];
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $app->injectRoutesFromConfig($config);
-
- $routes = $app->getRoutes();
-
- $route = array_shift($routes);
- $this->assertEquals($config['routes'][0]['options'], $route->getOptions());
- }
-
- public function testInjectRoutesFromConfigWillSkipSpecsThatOmitPath()
- {
- $config = [
- 'routes' => [
- [
- 'middleware' => new TestAsset\InteropMiddleware(),
- 'allowed_methods' => ['GET'],
- 'options' => [
- 'foo' => 'bar',
- ],
- ],
- ],
- ];
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
- $this->assertAttributeEquals([], 'routes', $app);
- }
-
- public function testInjectRoutesFromConfigWillSkipSpecsThatOmitMiddleware()
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'allowed_methods' => ['GET'],
- 'options' => [
- 'foo' => 'bar',
- ],
- ],
- ],
- ];
- $this->container->has('config')->willReturn(false);
- $app = $this->createApplication();
-
- $app->injectPipelineFromConfig($config);
- $this->assertAttributeEquals([], 'routes', $app);
- }
-
- public function testInjectPipelineFromConfigRaisesExceptionForSpecsOmittingMiddlewareKey()
- {
- $config = [
- 'middleware_pipeline' => [
- [
- 'this' => 'will not work',
- ],
- ],
- ];
- $app = $this->createApplication();
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Invalid pipeline specification received');
- $app->injectPipelineFromConfig($config);
- }
-}
diff --git a/test/Application/MarshalMiddlewareTraitTest.php b/test/Application/MarshalMiddlewareTraitTest.php
deleted file mode 100644
index f34bbb37..00000000
--- a/test/Application/MarshalMiddlewareTraitTest.php
+++ /dev/null
@@ -1,339 +0,0 @@
-container = $this->prophesize(ContainerInterface::class);
- $this->router = $this->prophesize(RouterInterface::class);
- $this->responsePrototype = $this->prophesize(ResponseInterface::class);
- $this->application = new Application($this->router->reveal());
- $this->disregardDeprecationNotices();
- }
-
- public function tearDown()
- {
- restore_error_handler();
- }
-
- public function disregardDeprecationNotices()
- {
- set_error_handler(function ($errno, $errstr) {
- if (strstr($errstr, 'pipe() the middleware directly')) {
- return true;
- }
- if (strstr($errstr, 'doublePassMiddleware()')) {
- return true;
- }
- return false;
- }, E_USER_DEPRECATED);
- }
-
- public function prepareMiddleware($middleware)
- {
- $r = new ReflectionMethod($this->application, 'prepareMiddleware');
- $r->setAccessible(true);
- return $r->invoke(
- $this->application,
- $middleware,
- $this->router->reveal(),
- $this->responsePrototype->reveal(),
- $this->container->reveal()
- );
- }
-
- public function prepareMiddlewareWithoutContainer($middleware)
- {
- $r = new ReflectionMethod($this->application, 'prepareMiddleware');
- $r->setAccessible(true);
- return $r->invoke(
- $this->application,
- $middleware,
- $this->router->reveal(),
- $this->responsePrototype->reveal()
- );
- }
-
- public function testPreparingRoutingMiddlewareReturnsRoutingMiddleware()
- {
- $middleware = $this->prepareMiddleware(Application::ROUTING_MIDDLEWARE);
- $this->assertInstanceOf(RouteMiddleware::class, $middleware);
- $this->assertAttributeSame($this->router->reveal(), 'router', $middleware);
- $this->assertAttributeSame($this->responsePrototype->reveal(), 'responsePrototype', $middleware);
- }
-
- public function testPreparingRoutingMiddlewareWithoutContainerReturnsRoutingMiddleware()
- {
- $middleware = $this->prepareMiddlewareWithoutContainer(Application::ROUTING_MIDDLEWARE);
- $this->assertInstanceOf(RouteMiddleware::class, $middleware);
- $this->assertAttributeSame($this->router->reveal(), 'router', $middleware);
- $this->assertAttributeSame($this->responsePrototype->reveal(), 'responsePrototype', $middleware);
- }
-
- public function testPreparingDispatchMiddlewareReturnsDispatchMiddleware()
- {
- $middleware = $this->prepareMiddleware(Application::DISPATCH_MIDDLEWARE);
- $this->assertInstanceOf(DispatchMiddleware::class, $middleware);
- }
-
- public function testPreparingDispatchMiddlewareWithoutContainerReturnsDispatchMiddleware()
- {
- $middleware = $this->prepareMiddlewareWithoutContainer(Application::DISPATCH_MIDDLEWARE);
- $this->assertInstanceOf(DispatchMiddleware::class, $middleware);
- }
-
- public function testPreparingInteropMiddlewareReturnsMiddlewareVerbatim()
- {
- $base = $this->prophesize(ServerMiddlewareInterface::class)->reveal();
- $middleware = $this->prepareMiddleware($base);
- $this->assertSame($base, $middleware);
- }
-
- public function testPreparingInteropMiddlewareWithoutContainerReturnsMiddlewareVerbatim()
- {
- $base = $this->prophesize(ServerMiddlewareInterface::class)->reveal();
- $middleware = $this->prepareMiddlewareWithoutContainer($base);
- $this->assertSame($base, $middleware);
- }
-
- public function testPreparingDuckTypedInteropMiddlewareReturnsDecoratedInteropMiddleware()
- {
- $base = function ($request, DelegateInterface $delegate) {
- };
- $middleware = $this->prepareMiddleware($base);
- $this->assertInstanceOf(CallableMiddlewareDecorator::class, $middleware);
- $this->assertAttributeSame($base, 'middleware', $middleware);
- }
-
- public function testPreparingDuckTypedInteropMiddlewareWithoutContainerReturnsDecoratedInteropMiddleware()
- {
- $base = function ($request, DelegateInterface $delegate) {
- };
- $middleware = $this->prepareMiddlewareWithoutContainer($base);
- $this->assertInstanceOf(CallableMiddlewareDecorator::class, $middleware);
- $this->assertAttributeSame($base, 'middleware', $middleware);
- }
-
- public function testPreparingCallableMiddlewareReturnsDecoratedMiddleware()
- {
- $base = function ($request, $response, callable $next) {
- };
- $middleware = $this->prepareMiddleware($base);
- $this->assertInstanceOf(DoublePassMiddlewareDecorator::class, $middleware);
- $this->assertAttributeSame($base, 'middleware', $middleware);
- $this->assertAttributeSame($this->responsePrototype->reveal(), 'responsePrototype', $middleware);
- }
-
- public function testPreparingCallableMiddlewareWithoutContainerReturnsDecoratedMiddleware()
- {
- $base = function ($request, $response, callable $next) {
- };
- $middleware = $this->prepareMiddlewareWithoutContainer($base);
- $this->assertInstanceOf(DoublePassMiddlewareDecorator::class, $middleware);
- $this->assertAttributeSame($base, 'middleware', $middleware);
- $this->assertAttributeSame($this->responsePrototype->reveal(), 'responsePrototype', $middleware);
- }
-
- public function testPreparingArrayOfMiddlewareReturnsMiddlewarePipe()
- {
- $first = $this->prophesize(ServerMiddlewareInterface::class)->reveal();
- $second = function ($request, DelegateInterface $delegate) {
- };
- $third = function ($request, $response, callable $next) {
- };
- $fourth = 'fourth';
- $fifth = TestAsset\CallableMiddleware::class;
- $sixth = TestAsset\CallableInteropMiddleware::class;
- $seventh = [$first, $second];
-
- $this->container->has('fourth')->willReturn(true);
- $this->container->has(TestAsset\CallableMiddleware::class)->willReturn(false);
- $this->container->has(TestAsset\CallableInteropMiddleware::class)->willReturn(false);
-
- $base = [
- $first,
- $second,
- $third,
- $fourth,
- $fifth,
- $sixth,
- $seventh,
- ];
-
- $middleware = $this->prepareMiddleware($base);
- $this->assertInstanceOf(MiddlewarePipe::class, $middleware);
-
- $r = new ReflectionProperty($middleware, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($middleware);
-
- $this->assertCount(7, $pipeline);
- }
-
- public function testPreparingArrayOfMiddlewareWithoutContainerReturnsMiddlewarePipe()
- {
- $first = $this->prophesize(ServerMiddlewareInterface::class)->reveal();
- $second = function ($request, DelegateInterface $delegate) {
- };
- $third = function ($request, $response, callable $next) {
- };
- $fifth = TestAsset\CallableMiddleware::class;
- $sixth = TestAsset\CallableInteropMiddleware::class;
-
- $base = [
- $first,
- $second,
- $third,
- $fifth,
- $sixth,
- ];
-
- $middleware = $this->prepareMiddleware($base);
- $this->assertInstanceOf(MiddlewarePipe::class, $middleware);
-
- $r = new ReflectionProperty($middleware, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($middleware);
-
- $this->assertCount(5, $pipeline);
- }
-
- public function testPreparingArrayOfMiddlewareRaisesExceptionWhenContainerMissingAndServiceInvalid()
- {
- $first = $this->prophesize(ServerMiddlewareInterface::class)->reveal();
- $second = 'second-middleware';
- $third = 'third-middleware';
-
- // No container is passed to the method, so this will not matter
- $this->container->has('second-middleware')->willReturn(true);
- $this->container->has('third-middleware')->willReturn(true);
-
- $base = [$first, $second, $third];
-
- $this->expectException(InvalidMiddlewareException::class);
- $this->expectExceptionMessage('second-middleware');
- $this->prepareMiddlewareWithoutContainer($base);
- }
-
- public function testPreparingServiceBasedMiddlewareReturnsLazyLoadingMiddleware()
- {
- $middlewareName = 'middleware';
- $this->container->has($middlewareName)->willReturn(true);
-
- $middleware = $this->prepareMiddleware($middlewareName);
- $this->assertInstanceOf(LazyLoadingMiddleware::class, $middleware);
-
- $this->assertAttributeSame($this->container->reveal(), 'container', $middleware);
- $this->assertAttributeSame($this->responsePrototype->reveal(), 'responsePrototype', $middleware);
- $this->assertAttributeEquals($middlewareName, 'middlewareName', $middleware);
- }
-
- public function testPreparingInvokableInteropMiddlewareReturnsDecoratedInteropMiddleware()
- {
- $base = TestAsset\CallableInteropMiddleware::class;
- $this->container->has(TestAsset\CallableInteropMiddleware::class)->willReturn(false);
-
- $middleware = $this->prepareMiddleware($base);
-
- $this->assertInstanceOf(CallableMiddlewareDecorator::class, $middleware);
- $this->assertAttributeInstanceOf(TestAsset\CallableInteropMiddleware::class, 'middleware', $middleware);
- }
-
- public function testPreparingInvokableCallableMiddlewareReturnsDecoratedMiddleware()
- {
- $base = TestAsset\CallableMiddleware::class;
- $this->container->has(TestAsset\CallableMiddleware::class)->willReturn(false);
- $middleware = $this->prepareMiddleware($base);
- $this->assertInstanceOf(DoublePassMiddlewareDecorator::class, $middleware);
- $this->assertAttributeInstanceOf(TestAsset\CallableMiddleware::class, 'middleware', $middleware);
- }
-
- public function testPreparingInvalidInvokableMiddlewareRaisesException()
- {
- $base = stdClass::class;
- $this->container->has(stdClass::class)->willReturn(false);
-
- $this->expectException(InvalidMiddlewareException::class);
- $this->expectExceptionMessage('invalid; neither invokable');
- $this->prepareMiddleware($base);
- }
-
- public function testPreparingInvokableInteropMiddlewareThatIsRegisteredInContainerReturnsLazyMiddleware()
- {
- $base = TestAsset\CallableMiddleware::class;
- $this->container->has(TestAsset\CallableMiddleware::class)->willReturn(true);
- $middleware = $this->prepareMiddleware($base);
-
- $this->assertInstanceOf(LazyLoadingMiddleware::class, $middleware);
- $this->assertAttributeEquals($base, 'middlewareName', $middleware);
- }
-
- public function invalidMiddlewareTypes()
- {
- $defaultExpectedMessage = 'Unable to resolve middleware';
- return [
- 'null' => [null, $defaultExpectedMessage],
- 'true' => [true, $defaultExpectedMessage],
- 'false' => [false, $defaultExpectedMessage],
- 'zero' => [0, $defaultExpectedMessage],
- 'int' => [1, $defaultExpectedMessage],
- 'zero-float' => [0.0, $defaultExpectedMessage],
- 'float' => [1.1, $defaultExpectedMessage],
- 'non-class-name-string' => ['not-a-class-name', 'not a valid class or service name'],
- 'non-callable-class-name' => [stdClass::class, 'invalid; neither invokable'],
- 'non-callable-object' => [new stdClass(), $defaultExpectedMessage],
- ];
- }
-
- /**
- * @dataProvider invalidMiddlewareTypes
- *
- * @param mixed $invalid
- * @param string $expectedMessage
- */
- public function testPreparingUnknownMiddlewareTypeRaisesException($invalid, $expectedMessage)
- {
- $this->expectException(InvalidMiddlewareException::class);
- $this->expectExceptionMessage($expectedMessage);
- $this->prepareMiddlewareWithoutContainer($invalid);
- }
-}
diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php
index 6ce96bbb..6f00210e 100644
--- a/test/ApplicationTest.php
+++ b/test/ApplicationTest.php
@@ -1,759 +1,343 @@
noopMiddleware = new TestAsset\InteropMiddleware();
- $this->router = $this->prophesize(RouterInterface::class);
- $this->disregardDeprecationNotices();
- }
-
- public function tearDown()
- {
- restore_error_handler();
- }
-
- public function disregardDeprecationNotices()
- {
- set_error_handler(function ($errno, $errstr) {
- if (strstr($errstr, 'pipe() the middleware directly')) {
- return true;
- }
- return false;
- }, E_USER_DEPRECATED);
- }
-
- public function getApp()
- {
- return new Application($this->router->reveal());
- }
-
- public function commonHttpMethods()
- {
- return [
- 'GET' => ['GET'],
- 'POST' => ['POST'],
- 'PUT' => ['PUT'],
- 'PATCH' => ['PATCH'],
- 'DELETE' => ['DELETE'],
- ];
- }
+ $this->factory = $this->prophesize(MiddlewareFactory::class);
+ $this->pipeline = $this->prophesize(MiddlewarePipeInterface::class);
+ $this->routes = $this->prophesize(RouteMiddleware::class);
+ $this->runner = $this->prophesize(RequestHandlerRunner::class);
- public function testConstructorAcceptsRouterAsAnArgument()
- {
- $app = $this->getApp();
- $this->assertInstanceOf(Application::class, $app);
- }
-
- public function testApplicationIsAMiddlewarePipe()
- {
- $app = $this->getApp();
- $this->assertInstanceOf(MiddlewarePipe::class, $app);
+ $this->app = new Application(
+ $this->factory->reveal(),
+ $this->pipeline->reveal(),
+ $this->routes->reveal(),
+ $this->runner->reveal()
+ );
}
- public function testRouteMethodReturnsRouteInstance()
+ public function createMockMiddleware()
{
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalled();
- $route = $this->getApp()->route('/foo', $this->noopMiddleware);
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals('/foo', $route->getPath());
- $this->assertSame($this->noopMiddleware, $route->getMiddleware());
+ return $this->prophesize(MiddlewareInterface::class)->reveal();
}
- public function testAnyRouteMethod()
+ public function testHandleProxiesToPipelineToHandle()
{
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalled();
- $route = $this->getApp()->any('/foo', $this->noopMiddleware);
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals('/foo', $route->getPath());
- $this->assertSame($this->noopMiddleware, $route->getMiddleware());
- $this->assertSame(Route::HTTP_METHOD_ANY, $route->getAllowedMethods());
- }
+ $request = $this->prophesize(ServerRequestInterface::class)->reveal();
+ $response = $this->prophesize(ResponseInterface::class)->reveal();
- /**
- * @dataProvider commonHttpMethods
- *
- * @param string $method
- */
- public function testCanCallRouteWithHttpMethods($method)
- {
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalled();
- $route = $this->getApp()->route('/foo', $this->noopMiddleware, [$method]);
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals('/foo', $route->getPath());
- $this->assertSame($this->noopMiddleware, $route->getMiddleware());
- $this->assertTrue($route->allowsMethod($method));
- $this->assertSame([$method], $route->getAllowedMethods());
- }
+ $this->pipeline->handle($request)->willReturn($response);
- public function testCanCallRouteWithMultipleHttpMethods()
- {
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalled();
- $methods = array_keys($this->commonHttpMethods());
- $route = $this->getApp()->route('/foo', $this->noopMiddleware, $methods);
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals('/foo', $route->getPath());
- $this->assertSame($this->noopMiddleware, $route->getMiddleware());
- $this->assertSame($methods, $route->getAllowedMethods());
+ $this->assertSame($response, $this->app->handle($request));
}
- public function testCanCallRouteWithARoute()
+ public function testProcessProxiesToPipelineToProcess()
{
- $route = new Route('/foo', $this->noopMiddleware);
- $this->router->addRoute($route)->shouldBeCalled();
- $app = $this->getApp();
- $test = $app->route($route);
- $this->assertSame($route, $test);
- }
+ $request = $this->prophesize(ServerRequestInterface::class)->reveal();
+ $response = $this->prophesize(ResponseInterface::class)->reveal();
+ $handler = $this->prophesize(RequestHandlerInterface::class)->reveal();
- public function testCallingRouteWithExistingPathAndOmittingMethodsArgumentRaisesException()
- {
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalledTimes(2);
- $app = $this->getApp();
- $app->route('/foo', $this->noopMiddleware);
- $app->route('/bar', $this->noopMiddleware);
- $this->expectException(DomainException::class);
- $app->route('/foo', function ($req, $res, $next) {
- });
- }
+ $this->pipeline->process($request, $handler)->willReturn($response);
- public function testCallingRouteWithOnlyAPathRaisesAnException()
- {
- $app = $this->getApp();
- $this->expectException(Exception\InvalidArgumentException::class);
- $app->route('/path');
+ $this->assertSame($response, $this->app->process($request, $handler));
}
- public function invalidPathTypes()
+ public function testRunProxiesToRunner()
{
- return [
- 'null' => [null],
- 'true' => [true],
- 'false' => [false],
- 'zero' => [0],
- 'int' => [1],
- 'zero-float' => [0.0],
- 'float' => [1.1],
- 'array' => [['path' => 'route']],
- 'object' => [(object) ['path' => 'route']],
- ];
+ $this->runner->run(null)->shouldBeCalled();
+ $this->assertNull($this->app->run());
}
- /**
- * @dataProvider invalidPathTypes
- *
- * @param mixed $path
- */
- public function testCallingRouteWithAnInvalidPathTypeRaisesAnException($path)
+ public function validMiddleware() : iterable
{
- $app = $this->getApp();
- $this->expectException(RouterException\InvalidArgumentException::class);
- $app->route($path, new TestAsset\InteropMiddleware());
+ // @codingStandardsIgnoreStart
+ yield 'string' => ['service'];
+ yield 'array' => [['middleware', 'service']];
+ yield 'callable' => [function ($request, $response) {}];
+ yield 'instance' => [new MiddlewarePipe()];
+ // @codingStandardsIgnoreEnd
}
/**
- * @dataProvider commonHttpMethods
- *
- * @param mixed $method
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testCommonHttpMethodsAreExposedAsClassMethodsAndReturnRoutes($method)
- {
- $app = $this->getApp();
- $route = $app->{$method}('/foo', $this->noopMiddleware);
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals('/foo', $route->getPath());
- $this->assertSame($this->noopMiddleware, $route->getMiddleware());
- $this->assertEquals([$method], $route->getAllowedMethods());
- }
-
- public function testCreatingHttpRouteMethodWithExistingPathButDifferentMethodCreatesNewRouteInstance()
- {
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalledTimes(2);
- $app = $this->getApp();
- $route = $app->route('/foo', $this->noopMiddleware, []);
-
- $middleware = new TestAsset\InteropMiddleware();
- $test = $app->get('/foo', $middleware);
- $this->assertNotSame($route, $test);
- $this->assertSame($route->getPath(), $test->getPath());
- $this->assertSame(['GET'], $test->getAllowedMethods());
- $this->assertSame($middleware, $test->getMiddleware());
- }
-
- public function testCreatingHttpRouteWithExistingPathAndMethodRaisesException()
- {
- $this->router->addRoute(Argument::type(Route::class))->shouldBeCalledTimes(1);
- $app = $this->getApp();
- $app->get('/foo', $this->noopMiddleware);
-
- $this->expectException(DomainException::class);
- $app->get('/foo', function ($req, $res, $next) {
- });
- }
-
- public function testRouteAndDispatchMiddlewareAreNotPipedAtInstantation()
+ public function testPipeCanAcceptSingleMiddlewareArgument($middleware)
{
- $app = $this->getApp();
+ $preparedMiddleware = $this->createMockMiddleware();
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $this->pipeline
+ ->pipe(Argument::that(function ($test) use ($preparedMiddleware) {
+ Assert::assertSame($preparedMiddleware, $test);
+ return $test;
+ }))
+ ->shouldBeCalled();
- $this->assertCount(0, $pipeline);
+ $this->assertNull($this->app->pipe($middleware));
}
- public function testCannotPipeRouteMiddlewareMoreThanOnce()
+ /**
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
+ */
+ public function testPipeCanAcceptAPathArgument($middleware)
{
- $app = $this->getApp();
- $routeMiddleware = Application::ROUTING_MIDDLEWARE;
-
- $app->pipe($routeMiddleware);
- $app->pipe($routeMiddleware);
-
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $preparedMiddleware = $this->createMockMiddleware();
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $this->assertCount(1, $pipeline);
- $route = $pipeline->dequeue();
- $this->assertInstanceOf(StratigilityRoute::class, $route);
- $test = $route->handler;
+ $this->pipeline
+ ->pipe(Argument::that(function ($test) use ($preparedMiddleware) {
+ Assert::assertInstanceOf(PathMiddlewareDecorator::class, $test);
+ Assert::assertAttributeSame('/foo', 'prefix', $test);
+ Assert::assertAttributeSame($preparedMiddleware, 'middleware', $test);
+ return $test;
+ }))
+ ->shouldBeCalled();
- $this->assertInstanceOf(RouteMiddleware::class, $test);
+ $this->assertNull($this->app->pipe('/foo', $middleware));
}
- public function testCannotPipeDispatchMiddlewareMoreThanOnce()
+ public function testPipeNonSlashPathOnNonStringPipeProduceTypeError()
{
- $app = $this->getApp();
- $dispatchMiddleware = Application::DISPATCH_MIDDLEWARE;
-
- $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->assertInstanceOf(DispatchMiddleware::class, $test);
- }
+ $middleware1 = function ($request, $response) {
+ return $response;
+ };
+ $middleware2 = $this->createMockMiddleware();
- public function testCanInjectDefaultDelegateViaConstructor()
- {
- $defaultDelegate = $this->prophesize(DelegateInterface::class)->reveal();
- $app = new Application($this->router->reveal(), null, $defaultDelegate);
- $test = $app->getDefaultDelegate();
- $this->assertSame($defaultDelegate, $test);
+ $this->expectException(TypeError::class);
+ $this->app->pipe($middleware1, $middleware2);
}
- public function testDefaultDelegateIsUsedAtInvocationIfNoOutArgumentIsSupplied()
+ /**
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
+ */
+ public function testRouteAcceptsPathAndMiddlewareOnly($middleware)
{
- $routeResult = RouteResult::fromRouteFailure();
- $this->router->match()->willReturn($routeResult);
-
- $finalResponse = $this->prophesize(ResponseInterface::class)->reveal();
- $defaultDelegate = $this->prophesize(DelegateInterface::class);
- $defaultDelegate->process(Argument::type(ServerRequestInterface::class))
- ->willReturn($finalResponse);
+ $preparedMiddleware = $this->createMockMiddleware();
- $emitter = $this->prophesize(EmitterInterface::class);
- $emitter->emit($finalResponse)->shouldBeCalled();
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $app = new Application($this->router->reveal(), null, $defaultDelegate->reveal(), $emitter->reveal());
+ $route = $this->prophesize(Route::class)->reveal();
- $request = new Request([], [], 'http://example.com/');
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ null,
+ null
+ )
+ ->willReturn($route);
- $app->run($request);
+ $this->assertSame($route, $this->app->route('/foo', $middleware));
}
- public function testComposesEmitterStackWithSapiEmitterByDefault()
- {
- $app = $this->getApp();
- $stack = $app->getEmitter();
- $this->assertInstanceOf(EmitterStack::class, $stack);
-
- $this->assertCount(1, $stack);
- $test = $stack->pop();
- $this->assertInstanceOf(SapiEmitter::class, $test);
- }
-
- public function testAllowsInjectingEmitterAtInstantiation()
- {
- $emitter = $this->prophesize(EmitterInterface::class);
- $app = new Application(
- $this->router->reveal(),
- null,
- null,
- $emitter->reveal()
- );
- $test = $app->getEmitter();
- $this->assertSame($emitter->reveal(), $test);
- }
-
- public function testComposedEmitterIsCalledByRun()
+ /**
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
+ */
+ public function testRouteAcceptsPathMiddlewareAndMethodsOnly($middleware)
{
- $routeResult = RouteResult::fromRouteFailure();
- $this->router->match()->willReturn($routeResult);
-
- $finalResponse = $this->prophesize(ResponseInterface::class)->reveal();
- $defaultDelegate = $this->prophesize(DelegateInterface::class);
- $defaultDelegate->process(Argument::type(ServerRequestInterface::class))
- ->willReturn($finalResponse);
+ $preparedMiddleware = $this->createMockMiddleware();
- $emitter = $this->prophesize(EmitterInterface::class);
- $emitter->emit(
- Argument::type(ResponseInterface::class)
- )->shouldBeCalled();
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $app = new Application($this->router->reveal(), null, $defaultDelegate->reveal(), $emitter->reveal());
+ $route = $this->prophesize(Route::class)->reveal();
- $request = new Request([], [], 'http://example.com/');
- $response = $this->prophesize(ResponseInterface::class);
- $response->withStatus(StatusCode::STATUS_NOT_FOUND)->will([$response, 'reveal']);
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ ['GET', 'POST'],
+ null
+ )
+ ->willReturn($route);
- $app->run($request, $response->reveal());
- }
-
- public function testCallingGetContainerReturnsComposedInstance()
- {
- $container = $this->prophesize(ContainerInterface::class);
- $app = new Application($this->router->reveal(), $container->reveal());
- $this->assertSame($container->reveal(), $app->getContainer());
- }
-
- public function testCallingGetContainerWhenNoContainerComposedWillRaiseException()
- {
- $app = new Application($this->router->reveal());
- $this->expectException(RuntimeException::class);
- $app->getContainer();
+ $this->assertSame($route, $this->app->route('/foo', $middleware, ['GET', 'POST']));
}
/**
- * @group 64
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testCanTriggerPipingOfRouteMiddleware()
+ public function testRouteAcceptsPathMiddlewareMethodsAndName($middleware)
{
- $app = $this->getApp();
- $app->pipeRoutingMiddleware();
+ $preparedMiddleware = $this->createMockMiddleware();
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $this->assertCount(1, $pipeline);
+ $route = $this->prophesize(Route::class)->reveal();
- $route = $pipeline->dequeue();
- $this->assertInstanceOf(StratigilityRoute::class, $route);
- $this->assertInstanceOf(RouteMiddleware::class, $route->handler);
- $this->assertEquals('/', $route->path);
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ ['GET', 'POST'],
+ 'foo'
+ )
+ ->willReturn($route);
+
+ $this->assertSame($route, $this->app->route('/foo', $middleware, ['GET', 'POST'], 'foo'));
}
- public function testCanTriggerPipingOfDispatchMiddleware()
+ public function requestMethodsWithValidMiddleware() : iterable
{
- $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->assertInstanceOf(DispatchMiddleware::class, $route->handler);
- $this->assertEquals('/', $route->path);
+ foreach (['get', 'post', 'put', 'patch', 'delete'] as $method) {
+ foreach ($this->validMiddleware() as $key => $data) {
+ array_unshift($data, $method);
+ $name = sprintf('%s-%s', $method, $key);
+ yield $name => $data;
+ }
+ }
}
/**
- * @group lazy-piping
+ * @dataProvider requestMethodsWithValidMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testPipingAllowsPassingMiddlewareServiceNameAsSoleArgument()
+ public function testSpecificRouteMethodsCanAcceptOnlyPathAndMiddleware(string $method, $middleware)
{
- $middleware = new TestAsset\InteropMiddleware();
+ $preparedMiddleware = $this->createMockMiddleware();
- $container = $this->mockContainerInterface();
- $this->injectServiceInContainer($container, 'foo', $middleware);
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $app = new Application($this->router->reveal(), $container->reveal());
- $app->pipe('foo');
+ $route = $this->prophesize(Route::class)->reveal();
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ [strtoupper($method)],
+ null
+ )
+ ->willReturn($route);
- $route = $pipeline->dequeue();
- $this->assertInstanceOf(StratigilityRoute::class, $route);
- $handler = $route->handler;
- $this->assertInstanceOf(Middleware\LazyLoadingMiddleware::class, $handler);
- $this->assertAttributeEquals('foo', 'middlewareName', $handler);
+ $this->assertSame($route, $this->app->{$method}('/foo', $middleware));
}
/**
- * @group lazy-piping
+ * @dataProvider requestMethodsWithValidMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testAllowsPipingMiddlewareAsServiceNameWithPath()
+ public function testSpecificRouteMethodsCanAcceptPathMiddlewareAndName(string $method, $middleware)
{
- $middleware = new TestAsset\InteropMiddleware();
+ $preparedMiddleware = $this->createMockMiddleware();
- $container = $this->mockContainerInterface();
- $this->injectServiceInContainer($container, 'foo', $middleware);
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $app = new Application($this->router->reveal(), $container->reveal());
- $app->pipe('/foo', 'foo');
+ $route = $this->prophesize(Route::class)->reveal();
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ [strtoupper($method)],
+ 'foo'
+ )
+ ->willReturn($route);
- $route = $pipeline->dequeue();
- $this->assertInstanceOf(StratigilityRoute::class, $route);
- $handler = $route->handler;
- $this->assertInstanceOf(PathMiddlewareDecorator::class, $handler);
-
- $r = new ReflectionProperty($handler, 'middleware');
- $r->setAccessible(true);
- $handler = $r->getValue($handler);
-
- $this->assertAttributeEquals('foo', 'middlewareName', $handler);
+ $this->assertSame($route, $this->app->{$method}('/foo', $middleware, 'foo'));
}
/**
- * @group lazy-piping
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testPipingNotInvokableMiddlewareRaisesExceptionWhenInvokingRoute()
+ public function testAnyMethodPassesNullForMethodWhenNoNamePresent($middleware)
{
- $middleware = 'not callable';
-
- $container = $this->mockContainerInterface();
- $this->injectServiceInContainer($container, 'foo', $middleware);
-
- $app = new Application($this->router->reveal(), $container->reveal());
- $app->pipe('/foo', 'foo');
+ $preparedMiddleware = $this->createMockMiddleware();
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $route = $pipeline->dequeue();
- $this->assertInstanceOf(StratigilityRoute::class, $route);
- $handler = $route->handler;
+ $route = $this->prophesize(Route::class)->reveal();
- $request = new ServerRequest([], [], '/foo', RequestMethod::METHOD_GET);
-
- $delegate = $this->prophesize(DelegateInterface::class)->reveal();
-
- $this->expectException(InvalidMiddlewareException::class);
- $handler->process($request, $delegate);
- }
-
- public function invalidRequestExceptions()
- {
- return [
- 'invalid file' => [
- InvalidArgumentException::class,
- 'Invalid value in files specification',
- ],
- 'invalid protocol version' => [
- UnexpectedValueException::class,
- 'Unrecognized protocol version (foo-bar)',
- ],
- ];
- }
-
- /**
- * @runInSeparateProcess
- * @preserveGlobalState disabled
- *
- * @dataProvider invalidRequestExceptions
- *
- * @param string $expectedException
- * @param string $message
- */
- public function testRunReturnsResponseWithBadRequestStatusWhenServerRequestFactoryRaisesException(
- $expectedException,
- $message
- ) {
- // try/catch is necessary in the case that the test fails.
- // Assertion exceptions raised inside prophecy expectations normally
- // are fine, but in the context of runInSeparateProcess, these
- // lead to closure serialization errors. try/catch allows us to
- // catch those and provide failure assertions.
- try {
- Mockery::mock('alias:' . ServerRequestFactory::class)
- ->shouldReceive('fromGlobals')
- ->withNoArgs()
- ->andThrow($expectedException, $message)
- ->once()
- ->getMock();
-
- $emitter = $this->prophesize(EmitterInterface::class);
- $emitter->emit(Argument::that(function ($response) {
- $this->assertInstanceOf(ResponseInterface::class, $response, 'Emitter did not receive a response');
- $this->assertEquals(StatusCode::STATUS_BAD_REQUEST, $response->getStatusCode());
- return true;
- }))->shouldBeCalled();
-
- $app = new Application($this->router->reveal(), null, null, $emitter->reveal());
-
- $app->run();
- } catch (\Throwable $e) {
- $this->fail($e->getMessage());
- } catch (\Exception $e) {
- $this->fail($e->getMessage());
- }
- }
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ null,
+ null
+ )
+ ->willReturn($route);
- public function testRetrieveRegisteredRoutes()
- {
- $route = new Route('/foo', $this->noopMiddleware);
- $this->router->addRoute($route)->shouldBeCalled();
- $app = $this->getApp();
- $test = $app->route($route);
- $this->assertSame($route, $test);
- $routes = $app->getRoutes();
- $this->assertCount(1, $routes);
- $this->assertInstanceOf(Route::class, $routes[0]);
+ $this->assertSame($route, $this->app->any('/foo', $middleware));
}
/**
- * This test verifies that if the ErrorResponseGenerator service is available,
- * it will be used to generate a response related to exceptions raised when
- * creating the server request.
- *
- * @runInSeparateProcess
- * @preserveGlobalState disabled
- *
- * @dataProvider invalidRequestExceptions
- *
- * @param string $expectedException
- * @param string $message
+ * @dataProvider validMiddleware
+ * @param string|array|callable|MiddlewareInterface $middleware
*/
- public function testRunReturnsResponseGeneratedByErrorResponseGeneratorWhenServerRequestFactoryRaisesException(
- $expectedException,
- $message
- ) {
- // try/catch is necessary in the case that the test fails.
- // Assertion exceptions raised inside prophecy expectations normally
- // are fine, but in the context of runInSeparateProcess, these
- // lead to closure serialization errors. try/catch allows us to
- // catch those and provide failure assertions.
- try {
- $generator = $this->prophesize(Middleware\ErrorResponseGenerator::class);
- $generator
- ->__invoke(
- Argument::type($expectedException),
- Argument::type(ServerRequestInterface::class),
- Argument::type(ResponseInterface::class)
- )->will(function ($args) {
- return $args[2];
- });
-
- $container = $this->mockContainerInterface();
- $this->injectServiceInContainer($container, Middleware\ErrorResponseGenerator::class, $generator);
-
- Mockery::mock('alias:' . ServerRequestFactory::class)
- ->shouldReceive('fromGlobals')
- ->withNoArgs()
- ->andThrow($expectedException, $message)
- ->once()
- ->getMock();
-
- $expectedResponse = $this->prophesize(ResponseInterface::class)->reveal();
-
- $emitter = $this->prophesize(EmitterInterface::class);
- $emitter->emit(Argument::that(function ($response) use ($expectedResponse) {
- $this->assertSame($expectedResponse, $response, 'Unexpected response provided to emitter');
- return true;
- }))->shouldBeCalled();
-
- $app = new Application($this->router->reveal(), $container->reveal(), null, $emitter->reveal());
- $app->setResponsePrototype($expectedResponse);
-
- $app->run();
- } catch (\Throwable $e) {
- $this->fail(sprintf("(%d) %s:\n%s", $e->getCode(), $e->getMessage(), $e->getTraceAsString()));
- } catch (\Exception $e) {
- $this->fail(sprintf("(%d) %s:\n%s", $e->getCode(), $e->getMessage(), $e->getTraceAsString()));
- }
- }
-
- public function testGetDefaultDelegateWillPullFromContainerIfServiceRegistered()
+ public function testAnyMethodPassesNullForMethodWhenAllArgumentsPresent($middleware)
{
- $delegate = $this->prophesize(DelegateInterface::class)->reveal();
- $container = $this->mockContainerInterface();
- $this->injectServiceInContainer($container, 'Zend\Expressive\Delegate\DefaultDelegate', $delegate);
+ $preparedMiddleware = $this->createMockMiddleware();
- $app = new Application($this->router->reveal(), $container->reveal());
+ $this->factory
+ ->prepare($middleware)
+ ->willReturn($preparedMiddleware);
- $test = $app->getDefaultDelegate();
+ $route = $this->prophesize(Route::class)->reveal();
- $this->assertSame($delegate, $test);
- }
+ $this->routes
+ ->route(
+ '/foo',
+ $preparedMiddleware,
+ null,
+ 'foo'
+ )
+ ->willReturn($route);
- public function testWillCreateAndConsumeNotFoundDelegateFactoryToCreateDelegateIfNoDelegateInContainer()
- {
- $container = $this->mockContainerInterface();
- $container->has('Zend\Expressive\Delegate\DefaultDelegate')->willReturn(false);
- $container->has(TemplateRendererInterface::class)->willReturn(false);
- $app = new Application($this->router->reveal(), $container->reveal());
-
- $delegate = $app->getDefaultDelegate();
-
- $this->assertInstanceOf(Delegate\NotFoundDelegate::class, $delegate);
-
- $r = new ReflectionProperty($app, 'responsePrototype');
- $r->setAccessible(true);
- $appResponsePrototype = $r->getValue($app);
-
- $this->assertAttributeNotSame($appResponsePrototype, 'responsePrototype', $delegate);
- $this->assertAttributeEmpty('renderer', $delegate);
+ $this->assertSame($route, $this->app->any('/foo', $middleware, 'foo'));
}
- public function testWillUseConfiguredTemplateRendererWhenCreatingDelegateFromNotFoundDelegateFactory()
+ public function testGetRoutesProxiesToRouteMiddleware()
{
- $container = $this->mockContainerInterface();
- $container->has('Zend\Expressive\Delegate\DefaultDelegate')->willReturn(false);
-
- $renderer = $this->prophesize(TemplateRendererInterface::class)->reveal();
- $this->injectServiceInContainer($container, TemplateRendererInterface::class, $renderer);
-
- $app = new Application($this->router->reveal(), $container->reveal());
-
- $delegate = $app->getDefaultDelegate();
-
- $this->assertInstanceOf(Delegate\NotFoundDelegate::class, $delegate);
-
- $r = new ReflectionProperty($app, 'responsePrototype');
- $r->setAccessible(true);
- $appResponsePrototype = $r->getValue($app);
-
- $this->assertAttributeNotSame($appResponsePrototype, 'responsePrototype', $delegate);
- $this->assertAttributeSame($renderer, 'renderer', $delegate);
- }
-
- public function testAllowsNestedMiddlewarePipelines()
- {
- $app = $this->getApp();
- $counter = function (ServerRequestInterface $request, DelegateInterface $delegate) {
- $count = $request->getAttribute('count', 0);
- $request = $request->withAttribute('count', $count + 1);
-
- return $delegate->process($request);
- };
-
- $app->pipe([
- // First level
- $counter,
- [
- // Second level
- $counter,
- $counter
- ],
- [
- [
- // Third level
- $counter,
- $counter
- ]
- ]
- ]);
-
- $request = new ServerRequest();
- $response = new Response();
- $delegate = $this->prophesize(DelegateInterface::class);
-
- $delegate->process($request->withAttribute('count', 5))
- ->shouldBeCalled()
- ->willReturn($response);
-
- $this->assertSame($response, $app->process($request, $delegate->reveal()));
- }
-
- public function testPreparingArrayWithPairOfObjectAndStringMiddlewaresShouldNotBeTreatedAsCallable()
- {
- $first = $this->prophesize(MiddlewareInterface::class)->reveal();
- $second = TestAsset\CallableInteropMiddleware::class;
- $queue = [$first, $second];
-
- $router = $this->router->reveal();
- $response = $this->prophesize(ResponseInterface::class)->reveal();
-
- $app = $this->getApp();
- $r = new ReflectionMethod($app, 'prepareMiddleware');
- $r->setAccessible(true);
-
- $middleware = $r->invoke($app, $queue, $router, $response);
-
- $this->assertInstanceOf(MiddlewarePipe::class, $middleware);
+ $route = $this->prophesize(Route::class)->reveal();
+ $this->routes->getRoutes()->willReturn([$route]);
- $r = new ReflectionProperty($middleware, 'pipeline');
- $r->setAccessible(true);
- $this->assertCount(2, $r->getValue($middleware));
+ $this->assertSame([$route], $this->app->getRoutes());
}
}
diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php
index eeb4338d..861b1f2c 100644
--- a/test/ConfigProviderTest.php
+++ b/test/ConfigProviderTest.php
@@ -1,26 +1,40 @@
provider->getDependencies();
$aliases = $config['aliases'];
- $this->assertArrayHasKey(Middleware\DispatchMiddleware::class, $aliases);
- $this->assertArrayHasKey(Middleware\ImplicitHeadMiddleware::class, $aliases);
- $this->assertArrayHasKey(Middleware\ImplicitOptionsMiddleware::class, $aliases);
- $this->assertArrayHasKey(Middleware\RouteMiddleware::class, $aliases);
- $this->assertArrayHasKey(NotFoundDelegate::class, $aliases);
- $this->assertArrayHasKey('Zend\Expressive\Delegate\DefaultDelegate', $aliases);
- }
-
- public function testProviderDefinesExpectedInvokableServices()
- {
- $config = $this->provider->getDependencies();
- $invokables = $config['invokables'];
- $this->assertArrayHasKey(RouterMiddleware\DispatchMiddleware::class, $invokables);
+ $this->assertArrayHasKey(DEFAULT_DELEGATE, $aliases);
+ $this->assertArrayHasKey(DISPATCH_MIDDLEWARE, $aliases);
+ $this->assertArrayHasKey(IMPLICIT_HEAD_MIDDLEWARE, $aliases);
+ $this->assertArrayHasKey(IMPLICIT_OPTIONS_MIDDLEWARE, $aliases);
+ $this->assertArrayHasKey(NOT_FOUND_MIDDLEWARE, $aliases);
+ $this->assertArrayHasKey(ROUTE_MIDDLEWARE, $aliases);
}
public function testProviderDefinesExpectedFactoryServices()
@@ -58,22 +65,67 @@ public function testProviderDefinesExpectedFactoryServices()
$this->assertArrayHasKey(Application::class, $factories);
$this->assertArrayHasKey(ErrorHandler::class, $factories);
$this->assertArrayHasKey(Middleware\ErrorResponseGenerator::class, $factories);
- $this->assertArrayHasKey(Middleware\NotFoundHandler::class, $factories);
$this->assertArrayHasKey(NotFoundHandler::class, $factories);
$this->assertArrayHasKey(ResponseInterface::class, $factories);
$this->assertArrayHasKey(StreamInterface::class, $factories);
- $this->assertArrayHasKey(RouterMiddleware\ImplicitHeadMiddleware::class, $factories);
- $this->assertArrayHasKey(RouterMiddleware\ImplicitOptionsMiddleware::class, $factories);
- $this->assertArrayHasKey(RouterMiddleware\RouteMiddleware::class, $factories);
}
public function testInvocationReturnsArrayWithDependencies()
{
- $config = $this->provider->__invoke();
+ $config = ($this->provider)();
$this->assertInternalType('array', $config);
$this->assertArrayHasKey('dependencies', $config);
$this->assertArrayHasKey('aliases', $config['dependencies']);
- $this->assertArrayHasKey('invokables', $config['dependencies']);
$this->assertArrayHasKey('factories', $config['dependencies']);
}
+
+ public function testServicesDefinedInConfigProvider()
+ {
+ $config = ($this->provider)();
+
+ $json = json_decode(
+ file_get_contents(__DIR__ . '/../composer.lock'),
+ true
+ );
+ foreach ($json['packages'] as $package) {
+ if (isset($package['extra']['zf']['config-provider'])) {
+ $configProvider = new $package['extra']['zf']['config-provider']();
+ $config = array_merge_recursive($config, $configProvider());
+ }
+ }
+
+ $routerInterface = $this->prophesize(RouterInterface::class)->reveal();
+ $config['dependencies']['services'][RouterInterface::class] = $routerInterface;
+ $container = $this->getContainer($config['dependencies']);
+
+ $dependencies = $this->provider->getDependencies();
+ foreach ($dependencies['factories'] as $name => $factory) {
+ $this->assertTrue($container->has($name), sprintf('Container does not contain service %s', $name));
+ $this->assertInternalType(
+ 'object',
+ $container->get($name),
+ sprintf('Cannot get service %s from container using factory %s', $name, $factory)
+ );
+ }
+
+ foreach ($dependencies['aliases'] as $alias => $dependency) {
+ $this->assertTrue(
+ $container->has($alias),
+ sprintf('Container does not contain service with alias %s', $alias)
+ );
+ $this->assertInternalType(
+ 'object',
+ $container->get($alias),
+ sprintf('Cannot get service %s using alias %s', $dependency, $alias)
+ );
+ }
+ }
+
+ private function getContainer(array $dependencies) : ServiceManager
+ {
+ $container = new ServiceManager();
+ (new Config($dependencies))->configureServiceManager($container);
+
+ return $container;
+ }
}
diff --git a/test/Container/ApplicationConfigInjectionDelegatorTest.php b/test/Container/ApplicationConfigInjectionDelegatorTest.php
index 939d2745..7e398062 100644
--- a/test/Container/ApplicationConfigInjectionDelegatorTest.php
+++ b/test/Container/ApplicationConfigInjectionDelegatorTest.php
@@ -1,18 +1,19 @@
dispatchMiddleware = $this->prophesize(DispatchMiddleware::class)->reveal();
+ $this->methodNotAllowedMiddleware = $this->prophesize(MethodNotAllowedMiddleware::class)->reveal();
}
public function createApplication()
{
+ $container = new MiddlewareContainer($this->container->reveal());
+ $factory = new MiddlewareFactory($container);
+ $pipeline = new MiddlewarePipe();
+ $runner = $this->prophesize(RequestHandlerRunner::class)->reveal();
return new Application(
- $this->router->reveal(),
- $this->container->reveal()
+ $factory,
+ $pipeline,
+ $this->routeMiddleware,
+ $runner
);
}
@@ -69,7 +83,11 @@ public function getQueueFromApplicationPipeline(Application $app)
{
$r = new ReflectionProperty($app, 'pipeline');
$r->setAccessible(true);
- return $r->getValue($app);
+ $pipeline = $r->getValue($app);
+
+ $r = new ReflectionProperty($pipeline, 'pipeline');
+ $r->setAccessible(true);
+ return $r->getValue($pipeline);
}
public static function assertRoute($spec, array $routes)
@@ -160,7 +178,26 @@ public static function assertDispatchMiddleware(MiddlewareInterface $middleware)
);
}
- public function injectableMiddleware()
+ public static function assertMethodNotAllowedMiddleware(MiddlewareInterface $middleware)
+ {
+ if ($middleware instanceof MethodNotAllowedMiddleware) {
+ Assert::assertInstanceOf(MethodNotAllowedMiddleware::class, $middleware);
+ return;
+ }
+
+ if (! $middleware instanceof Middleware\LazyLoadingMiddleware) {
+ Assert::fail('Middleware is not an instance of MethodNotAllowedMiddleware');
+ }
+
+ Assert::assertAttributeSame(
+ MethodNotAllowedMiddleware::class,
+ 'middlewareName',
+ $middleware,
+ 'Middleware is not an instance of MethodNotAllowedMiddleware'
+ );
+ }
+
+ public function callableMiddlewares()
{
return [
[CallableInteropMiddleware::class],
@@ -185,7 +222,7 @@ public function testInvocationAsDelegatorFactoryRaisesExceptionIfCallbackIsNotAn
}
/**
- * @dataProvider injectableMiddleware
+ * @dataProvider callableMiddlewares
*
* @param callable|array|string $middleware
*/
@@ -243,7 +280,7 @@ public function testNoRoutesAreAddedIfSpecDoesNotProvidePathOrMiddleware()
public function testInjectPipelineFromConfigHonorsPriorityOrderWhenAttachingMiddleware()
{
- $middleware = new InteropMiddleware();
+ $middleware = new TestAsset\InteropMiddleware();
$pipeline1 = [['middleware' => clone $middleware, 'priority' => 1]];
$pipeline2 = [['middleware' => clone $middleware, 'priority' => 100]];
@@ -258,14 +295,14 @@ public function testInjectPipelineFromConfigHonorsPriorityOrderWhenAttachingMidd
$pipeline = $this->getQueueFromApplicationPipeline($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);
+ $this->assertSame($pipeline2[0]['middleware'], $pipeline->dequeue());
+ $this->assertSame($pipeline1[0]['middleware'], $pipeline->dequeue());
+ $this->assertSame($pipeline3[0]['middleware'], $pipeline->dequeue());
}
public function testMiddlewareWithoutPriorityIsGivenDefaultPriorityAndRegisteredInOrderReceived()
{
- $middleware = new InteropMiddleware();
+ $middleware = new TestAsset\InteropMiddleware();
$pipeline1 = [['middleware' => clone $middleware]];
$pipeline2 = [['middleware' => clone $middleware]];
@@ -280,9 +317,9 @@ public function testMiddlewareWithoutPriorityIsGivenDefaultPriorityAndRegistered
$pipeline = $this->getQueueFromApplicationPipeline($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);
+ $this->assertSame($pipeline3[0]['middleware'], $pipeline->dequeue());
+ $this->assertSame($pipeline1[0]['middleware'], $pipeline->dequeue());
+ $this->assertSame($pipeline2[0]['middleware'], $pipeline->dequeue());
}
public function testInjectPipelineFromConfigWithEmptyConfigDoesNothing()
@@ -308,7 +345,7 @@ public function testInjectRoutesFromConfigRaisesExceptionIfAllowedMethodsIsInval
'routes' => [
[
'path' => '/',
- 'middleware' => new InteropMiddleware(),
+ 'middleware' => new TestAsset\InteropMiddleware(),
'allowed_methods' => 'not-valid',
],
],
@@ -327,7 +364,7 @@ public function testInjectRoutesFromConfigRaisesExceptionIfOptionsIsNotAnArray()
'routes' => [
[
'path' => '/',
- 'middleware' => new InteropMiddleware(),
+ 'middleware' => new TestAsset\InteropMiddleware(),
'allowed_methods' => ['GET'],
'options' => 'invalid',
],
@@ -347,7 +384,7 @@ public function testInjectRoutesFromConfigCanProvideRouteOptions()
'routes' => [
[
'path' => '/',
- 'middleware' => new InteropMiddleware(),
+ 'middleware' => new TestAsset\InteropMiddleware(),
'allowed_methods' => ['GET'],
'options' => [
'foo' => 'bar',
@@ -371,7 +408,7 @@ public function testInjectRoutesFromConfigWillSkipSpecsThatOmitPath()
$config = [
'routes' => [
[
- 'middleware' => new InteropMiddleware(),
+ 'middleware' => new TestAsset\InteropMiddleware(),
'allowed_methods' => ['GET'],
'options' => [
'foo' => 'bar',
diff --git a/test/Container/ApplicationFactoryTest.php b/test/Container/ApplicationFactoryTest.php
index 495fb326..d7ec0d48 100644
--- a/test/Container/ApplicationFactoryTest.php
+++ b/test/Container/ApplicationFactoryTest.php
@@ -1,614 +1,47 @@
container = $this->mockContainerInterface();
- $this->factory = new ApplicationFactory();
-
- $this->router = $this->prophesize(RouterInterface::class);
- $this->emitter = $this->prophesize(EmitterInterface::class);
- $this->delegate = $this->prophesize(DelegateInterface::class)->reveal();
-
- $this->injectServiceInContainer($this->container, RouterInterface::class, $this->router->reveal());
- $this->injectServiceInContainer($this->container, EmitterInterface::class, $this->emitter->reveal());
- $this->injectServiceInContainer($this->container, 'Zend\Expressive\Delegate\DefaultDelegate', $this->delegate);
-
- $this->disregardDeprecationNotices();
- }
-
- public function tearDown()
- {
- restore_error_handler();
- }
-
- public function disregardDeprecationNotices()
- {
- set_error_handler(function ($errno, $errstr) {
- if (strstr($errstr, 'pipe() the middleware directly')) {
- return true;
- }
- if (strstr($errstr, 'doublePassMiddleware()')) {
- return true;
- }
- return false;
- }, E_USER_DEPRECATED);
- }
-
- public static function assertRoute($spec, array $routes)
- {
- Assert::assertThat(
- array_reduce($routes, function ($found, $route) use ($spec) {
- if ($found) {
- return $found;
- }
-
- if ($route->getPath() !== $spec['path']) {
- return false;
- }
-
- // We're just testing that middleware is present; since it may
- // be decorated, this might fail otherwise.
- if (! $route->getMiddleware()) {
- return false;
- }
-
- if (isset($spec['allowed_methods'])) {
- if ($route->getAllowedMethods() !== $spec['allowed_methods']) {
- return false;
- }
- }
-
- if (! isset($spec['allowed_methods'])) {
- if ($route->getAllowedMethods() !== Route::HTTP_METHOD_ANY) {
- return false;
- }
- }
-
- return true;
- }, false),
- Assert::isTrue(),
- 'Route matching specification not found'
- );
- }
-
- public function getRouterFromApplication(Application $app)
- {
- $r = new ReflectionProperty($app, 'router');
- $r->setAccessible(true);
- return $r->getValue($app);
- }
-
- public function testFactoryWillPullAllReplaceableDependenciesFromContainerWhenPresent()
+ public function testFactoryProducesAnApplication()
{
- $app = $this->factory->__invoke($this->container->reveal());
- $this->assertInstanceOf(Application::class, $app);
- $test = $this->getRouterFromApplication($app);
- $this->assertSame($this->router->reveal(), $test);
- $this->assertSame($this->container->reveal(), $app->getContainer());
- $this->assertSame($this->emitter->reveal(), $app->getEmitter());
- $this->assertSame($this->delegate, $app->getDefaultDelegate());
- }
-
- public function injectableMiddleware()
- {
- $middlewareTypes = [
- [CallableInteropMiddleware::class],
- [
- function ($request, DelegateInterface $delegate) {
- },
- ],
- [[CallableInteropMiddleware::class, 'staticallyCallableMiddleware']],
- ];
-
- $configTypes = [
- 'array' => null,
- 'array-object' => ArrayObject::class,
- ];
-
- foreach ($configTypes as $configType => $config) {
- foreach ($middlewareTypes as $middlewareType => $middleware) {
- $name = sprintf('%s-%s', $configType, $middlewareType);
- yield $name => [$middleware, $config];
- }
- }
- }
-
- /**
- * @dataProvider injectableMiddleware
- *
- * @param callable|array|string $middleware
- * @param string $configType
- */
- public function testFactorySetsUpRoutesFromConfig($middleware, $configType)
- {
- $pingMiddleware = $this->prophesize(MiddlewareInterface::class)->reveal();
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => $middleware,
- 'allowed_methods' => ['GET'],
- ],
- [
- 'path' => '/ping',
- 'middleware' => $pingMiddleware,
- 'allowed_methods' => ['GET'],
- ],
- ],
- ];
+ $middlewareFactory = $this->prophesize(MiddlewareFactory::class)->reveal();
+ $pipeline = $this->prophesize(MiddlewarePipeInterface::class)->reveal();
+ $routeMiddleware = $this->prophesize(PathBasedRoutingMiddleware::class)->reveal();
+ $runner = $this->prophesize(RequestHandlerRunner::class)->reveal();
- $config = $configType ? new $configType($config) : $config;
+ $container = $this->prophesize(ContainerInterface::class);
+ $container->get(MiddlewareFactory::class)->willReturn($middlewareFactory);
+ $container->get(ApplicationPipeline::class)->willReturn($pipeline);
+ $container->get(PathBasedRoutingMiddleware::class)->willReturn($routeMiddleware);
+ $container->get(RequestHandlerRunner::class)->willReturn($runner);
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $app = $this->factory->__invoke($this->container->reveal());
-
- $routes = $app->getRoutes();
-
- foreach ($config['routes'] as $route) {
- $this->assertRoute($route, $routes);
- }
- }
-
- public function testWillUseSaneDefaultsForOptionalServices()
- {
- $container = $this->mockContainerInterface();
$factory = new ApplicationFactory();
- $app = $factory->__invoke($container->reveal());
- $this->assertInstanceOf(Application::class, $app);
- $router = $this->getRouterFromApplication($app);
- $this->assertInstanceOf(FastRouteRouter::class, $router);
- $this->assertSame($container->reveal(), $app->getContainer());
- $this->assertInstanceOf(EmitterStack::class, $app->getEmitter());
- $this->assertCount(1, $app->getEmitter());
- $this->assertInstanceOf(SapiEmitter::class, $app->getEmitter()->pop());
- $this->assertInstanceOf(NotFoundDelegate::class, $app->getDefaultDelegate());
- }
-
- public function configTypes()
- {
- return [
- 'array' => [null],
- 'array-object' => [ArrayObject::class],
- ];
- }
-
- /**
- * @dataProvider configTypes
- * @group piping
- *
- * @param null|string $configType
- */
- public function testMiddlewareIsNotAddedIfSpecIsInvalid($configType)
- {
- $config = [
- 'middleware_pipeline' => [
- ['foo' => 'bar'],
- ['path' => '/foo'],
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $this->expectException(ExpressiveException\InvalidArgumentException::class);
- $this->expectExceptionMessage('pipeline');
- $this->factory->__invoke($this->container->reveal());
- }
-
- /**
- * @dataProvider configTypes
- *
- * @param null|string $configType
- */
- public function testCanSpecifyRouteViaConfigurationWithNoMethods($configType)
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => $this->prophesize(MiddlewareInterface::class)->reveal(),
- ],
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $app = $this->factory->__invoke($this->container->reveal());
-
- $routes = $app->getRoutes();
-
- foreach ($config['routes'] as $route) {
- $this->assertRoute($route, $routes);
- }
- }
-
- /**
- * @dataProvider configTypes
- *
- * @param null|string $configType
- */
- public function testCanSpecifyRouteOptionsViaConfiguration($configType)
- {
- $expected = [
- 'values' => [
- 'foo' => 'bar',
- ],
- 'tokens' => [
- 'bar' => 'foo',
- ],
- ];
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => $this->prophesize(MiddlewareInterface::class)->reveal(),
- 'name' => 'home',
- 'allowed_methods' => ['GET'],
- 'options' => $expected,
- ],
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $app = $this->factory->__invoke($this->container->reveal());
-
- $routes = $app->getRoutes();
- $route = array_shift($routes);
-
- $this->assertInstanceOf(Route::class, $route);
- $this->assertEquals($expected, $route->getOptions());
- }
-
- /**
- * @dataProvider configTypes
- *
- * @param null|string $configType
- */
- public function testExceptionIsRaisedInCaseOfInvalidRouteMethodsConfiguration($configType)
- {
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => 'HelloWorld',
- 'allowed_methods' => 'invalid',
- ],
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $this->expectException(ExpressiveException\InvalidArgumentException::class);
- $this->expectExceptionMessage('route must be in form of an array; received "string"');
- $this->factory->__invoke($this->container->reveal());
- }
-
- /**
- * @dataProvider configTypes
- *
- * @param null|string $configType
- */
- public function testExceptionIsRaisedInCaseOfInvalidRouteOptionsConfiguration($configType)
- {
- $middleware = $this->prophesize(MiddlewareInterface::class)->reveal();
- $config = [
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => $middleware,
- 'options' => 'invalid',
- ],
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $this->expectException(ExpressiveException\InvalidArgumentException::class);
- $this->expectExceptionMessage('options must be an array; received "string"');
- $this->factory->__invoke($this->container->reveal());
- }
-
- /**
- * @dataProvider configTypes
- *
- * @param null|string $configType
- */
- public function testWillCreatePipelineBasedOnMiddlewareConfiguration($configType)
- {
- $api = new InteropMiddleware();
-
- $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];
- $config = $configType ? new $configType($config) : $config;
- $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'
- );
-
- $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);
-
- $this->assertCount(5, $pipeline, 'Did not get expected pipeline count!');
-
- $test = $pipeline->dequeue();
- $this->assertEquals('/', $test->path);
- $this->assertInstanceOf(PathMiddlewareDecorator::class, $test->handler);
- $handler = $test->handler;
- $this->assertAttributeSame('/api', 'prefix', $handler);
- $this->assertAttributeSame($api, 'middleware', $handler);
-
- // Lazy middleware is not marshaled until invocation
- $test = $pipeline->dequeue();
- $this->assertEquals('/', $test->path);
- $this->assertInstanceOf(PathMiddlewareDecorator::class, $test->handler);
- $handler = $test->handler;
- $this->assertAttributeSame('/dynamic-path', 'prefix', $handler);
- $this->assertAttributeNotSame($dynamicPath, 'middleware', $handler);
- $this->assertAttributeInstanceOf(LazyLoadingMiddleware::class, 'middleware', $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(LazyLoadingMiddleware::class, $test->handler);
-
- $test = $pipeline->dequeue();
- $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);
-
- // Lazy middleware is not marshaled until invocation
- $test = $nestedPipeline->dequeue();
- $this->assertNotSame($hello, $test->handler);
- $this->assertInstanceOf(LazyLoadingMiddleware::class, $test->handler);
-
- $test = $nestedPipeline->dequeue();
- $this->assertSame($pipelineLast, $test->handler);
- }
-
- public function configWithRoutesButNoPipeline()
- {
- $middleware = function ($request, $response, $next) {
- };
-
- $routes = [
- [
- 'path' => '/',
- 'middleware' => clone $middleware,
- 'allowed_methods' => ['GET'],
- ],
- ];
-
- $configs = [
- 'no-pipeline-defined' => ['routes' => $routes],
- 'empty-pipeline' => ['middleware_pipeline' => [], 'routes' => $routes],
- 'null-pipeline' => ['middleware_pipeline' => null, 'routes' => $routes],
- ];
-
- $configTypes = [
- 'array' => null,
- 'array-object' => ArrayObject::class,
- ];
-
- foreach ($configTypes as $configName => $configType) {
- foreach ($configs as $name => $config) {
- $caseName = sprintf('%s-%s', $configName, $name);
- yield $caseName => [$config, $configType];
- }
- }
- }
-
- /**
- * @dataProvider configWithRoutesButNoPipeline
- *
- * @param array $config
- * @param string|null $configType
- */
- public function testProvidingRoutesAndNoPipelineImplicitlyRegistersRoutingAndDispatchMiddleware(
- array $config,
- $configType
- ) {
- $config = $configType ? new $configType($config) : $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(2, $pipeline, 'Did not get expected pipeline count!');
-
- $test = $pipeline->dequeue();
- $this->assertEquals('/', $test->path);
- $this->assertInstanceOf(RouteMiddleware::class, $test->handler);
-
- $test = $pipeline->dequeue();
- $this->assertEquals('/', $test->path);
- $this->assertInstanceOf(DispatchMiddleware::class, $test->handler);
- }
-
- /**
- * @dataProvider configTypes
- * @group programmatic
- *
- * @param null|string $configType
- */
- public function testWillNotInjectConfiguredRoutesOrPipelineIfProgrammaticPipelineFlagEnabled($configType)
- {
- $api = new InteropMiddleware();
-
- $dynamicPath = clone $api;
- $noPath = clone $api;
- $goodbye = clone $api;
- $pipelineFirst = clone $api;
- $hello = clone $api;
- $pipelineLast = clone $api;
-
- $config = [
- 'middleware_pipeline' => [
- ['path' => '/api', 'middleware' => $api],
- ['path' => '/dynamic-path', 'middleware' => 'DynamicPath'],
- ['middleware' => $noPath],
- ['middleware' => 'Goodbye'],
- [
- 'middleware' => [
- $pipelineFirst,
- 'Hello',
- $pipelineLast,
- ],
- ],
- ],
- 'routes' => [
- [
- 'path' => '/',
- 'middleware' => 'HelloWorld',
- 'name' => 'home',
- 'allowed_methods' => ['GET'],
- 'options' => [],
- ],
- ],
- 'zend-expressive' => [
- 'programmatic_pipeline' => true,
- ],
- ];
-
- $config = $configType ? new $configType($config) : $config;
-
- $this->injectServiceInContainer($this->container, 'DynamicPath', $dynamicPath);
- $this->injectServiceInContainer($this->container, 'Goodbye', $goodbye);
- $this->injectServiceInContainer($this->container, 'Hello', $hello);
- $this->injectServiceInContainer($this->container, 'config', $config);
-
- $app = $this->factory->__invoke($this->container->reveal());
-
- $this->assertAttributeSame(false, 'routeMiddlewareIsRegistered', $app);
- $this->assertAttributeSame(false, 'dispatchMiddlewareIsRegistered', $app);
-
- $r = new ReflectionProperty($app, 'pipeline');
- $r->setAccessible(true);
- $pipeline = $r->getValue($app);
- $this->assertCount(0, $pipeline, 'Pipeline contains entries and should not');
+ $application = $factory($container->reveal());
- $routes = $app->getRoutes();
- $this->assertEmpty($routes, 'Routes exist, and should not');
+ $this->assertInstanceOf(Application::class, $application);
+ $this->assertAttributeSame($middlewareFactory, 'factory', $application);
+ $this->assertAttributeSame($pipeline, 'pipeline', $application);
+ $this->assertAttributeSame($routeMiddleware, 'routes', $application);
+ $this->assertAttributeSame($runner, 'runner', $application);
}
}
diff --git a/test/Container/EmitterFactoryTest.php b/test/Container/EmitterFactoryTest.php
new file mode 100644
index 00000000..cb95b331
--- /dev/null
+++ b/test/Container/EmitterFactoryTest.php
@@ -0,0 +1,34 @@
+prophesize(ContainerInterface::class)->reveal();
+ $factory = new EmitterFactory();
+
+ $emitter = $factory($container);
+
+ $this->assertInstanceOf(EmitterStack::class, $emitter);
+
+ $emitters = iterator_to_array($emitter);
+ $this->assertCount(1, $emitters);
+
+ $emitter = array_shift($emitters);
+ $this->assertInstanceOf(SapiEmitter::class, $emitter);
+ }
+}
diff --git a/test/Container/ErrorHandlerFactoryTest.php b/test/Container/ErrorHandlerFactoryTest.php
index a1ea5859..faa594a6 100644
--- a/test/Container/ErrorHandlerFactoryTest.php
+++ b/test/Container/ErrorHandlerFactoryTest.php
@@ -1,16 +1,21 @@
container = $this->prophesize(ContainerInterface::class);
}
+ public function testFactoryFailsIfResponseServiceIsMissing()
+ {
+ $exception = new RuntimeException();
+ $this->container->has(ErrorResponseGenerator::class)->willReturn(false);
+ $this->container->get(ErrorResponseGenerator::class)->shouldNotBeCalled();
+ $this->container->get(ResponseInterface::class)->willThrow($exception);
+
+ $factory = new ErrorHandlerFactory();
+
+ $this->expectException(RuntimeException::class);
+ $factory($this->container->reveal());
+ }
+
+ public function testFactoryFailsIfResponseServiceReturnsResponse()
+ {
+ $response = $this->prophesize(ResponseInterface::class)->reveal();
+ $this->container->has(ErrorResponseGenerator::class)->willReturn(false);
+ $this->container->get(ErrorResponseGenerator::class)->shouldNotBeCalled();
+ $this->container->get(ResponseInterface::class)->willReturn($response);
+
+ $factory = new ErrorHandlerFactory();
+
+ $this->expectException(TypeError::class);
+ $factory($this->container->reveal());
+ }
+
public function testFactoryCreatesHandlerWithStratigilityGeneratorIfNoGeneratorServiceAvailable()
{
$this->container->has(ErrorResponseGenerator::class)->willReturn(false);
+ $this->container->get(ErrorResponseGenerator::class)->shouldNotBeCalled();
+
+ $this->container->get(ResponseInterface::class)->willReturn(function () {
+ });
$factory = new ErrorHandlerFactory();
$handler = $factory($this->container->reveal());
$this->assertInstanceOf(ErrorHandler::class, $handler);
- $this->assertAttributeInstanceOf(ResponseInterface::class, 'responsePrototype', $handler);
+ $this->assertAttributeInstanceOf(Closure::class, 'responseFactory', $handler);
$this->assertAttributeInstanceOf(StratigilityGenerator::class, 'responseGenerator', $handler);
}
@@ -44,11 +79,14 @@ public function testFactoryCreatesHandlerWithGeneratorIfGeneratorServiceAvailabl
$this->container->has(ErrorResponseGenerator::class)->willReturn(true);
$this->container->get(ErrorResponseGenerator::class)->willReturn($generator);
+ $this->container->get(ResponseInterface::class)->willReturn(function () {
+ });
+
$factory = new ErrorHandlerFactory();
$handler = $factory($this->container->reveal());
$this->assertInstanceOf(ErrorHandler::class, $handler);
- $this->assertAttributeInstanceOf(ResponseInterface::class, 'responsePrototype', $handler);
+ $this->assertAttributeInstanceOf(Closure::class, 'responseFactory', $handler);
$this->assertAttributeSame($generator, 'responseGenerator', $handler);
}
}
diff --git a/test/Container/ErrorResponseGeneratorFactoryTest.php b/test/Container/ErrorResponseGeneratorFactoryTest.php
index d5a3d9d8..9872c615 100644
--- a/test/Container/ErrorResponseGeneratorFactoryTest.php
+++ b/test/Container/ErrorResponseGeneratorFactoryTest.php
@@ -1,10 +1,12 @@
prophesize(ContainerInterface::class)->reveal();
+ $factory = new MiddlewareContainerFactory();
+
+ $middlewareContainer = $factory($container);
+
+ $this->assertInstanceOf(MiddlewareContainer::class, $middlewareContainer);
+ $this->assertAttributeSame($container, 'container', $middlewareContainer);
+ }
+}
diff --git a/test/Container/MiddlewareFactoryFactoryTest.php b/test/Container/MiddlewareFactoryFactoryTest.php
new file mode 100644
index 00000000..481b004e
--- /dev/null
+++ b/test/Container/MiddlewareFactoryFactoryTest.php
@@ -0,0 +1,34 @@
+prophesize(MiddlewareContainer::class)->reveal();
+
+ $container = $this->prophesize(ContainerInterface::class);
+ $container->get(MiddlewareContainer::class)->willReturn($middlewareContainer);
+
+ $factory = new MiddlewareFactoryFactory();
+
+ $middlewareFactory = $factory($container->reveal());
+
+ $this->assertInstanceOf(MiddlewareFactory::class, $middlewareFactory);
+ $this->assertAttributeSame($middlewareContainer, 'container', $middlewareFactory);
+ }
+}
diff --git a/test/Container/NotFoundDelegateFactoryTest.php b/test/Container/NotFoundDelegateFactoryTest.php
deleted file mode 100644
index 4667025f..00000000
--- a/test/Container/NotFoundDelegateFactoryTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-container = $this->prophesize(ContainerInterface::class);
- }
-
- public function testFactoryCreatesInstanceWithoutRendererIfRendererServiceIsMissing()
- {
- $this->container->has('config')->willReturn(false);
- $this->container->has(TemplateRendererInterface::class)->willReturn(false);
- $factory = new NotFoundDelegateFactory();
-
- $delegate = $factory($this->container->reveal());
- $this->assertInstanceOf(NotFoundDelegate::class, $delegate);
- $this->assertAttributeInstanceOf(Response::class, 'responsePrototype', $delegate);
- $this->assertAttributeEmpty('renderer', $delegate);
- }
-
- public function testFactoryCreatesInstanceUsingRendererServiceWhenPresent()
- {
- $renderer = $this->prophesize(TemplateRendererInterface::class)->reveal();
- $this->container->has('config')->willReturn(false);
- $this->container->has(TemplateRendererInterface::class)->willReturn(true);
- $this->container->get(TemplateRendererInterface::class)->willReturn($renderer);
- $factory = new NotFoundDelegateFactory();
-
- $delegate = $factory($this->container->reveal());
- $this->assertAttributeSame($renderer, 'renderer', $delegate);
- }
-
- public function testFactoryUsesConfigured404TemplateWhenPresent()
- {
- $config = [
- 'zend-expressive' => [
- 'error_handler' => [
- 'layout' => 'layout::error',
- 'template_404' => 'foo::bar',
- ],
- ],
- ];
- $this->container->has('config')->willReturn(true);
- $this->container->get('config')->willReturn($config);
- $this->container->has(TemplateRendererInterface::class)->willReturn(false);
- $factory = new NotFoundDelegateFactory();
-
- $delegate = $factory($this->container->reveal());
- $this->assertAttributeEquals(
- $config['zend-expressive']['error_handler']['layout'],
- 'layout',
- $delegate
- );
- $this->assertAttributeEquals(
- $config['zend-expressive']['error_handler']['template_404'],
- 'template',
- $delegate
- );
- }
-}
diff --git a/test/Container/NotFoundHandlerFactoryTest.php b/test/Container/NotFoundHandlerFactoryTest.php
index 54580e29..63bb3d12 100644
--- a/test/Container/NotFoundHandlerFactoryTest.php
+++ b/test/Container/NotFoundHandlerFactoryTest.php
@@ -1,30 +1,88 @@
prophesize(NotFoundDelegate::class)->reveal();
- $container = $this->prophesize(ContainerInterface::class);
- $container->get(NotFoundDelegate::class)->willReturn($delegate);
- $factory = new NotFoundHandlerFactory();
+ $this->response = $this->prophesize(ResponseInterface::class)->reveal();
+ $this->container = $this->prophesize(ContainerInterface::class);
+ $this->container->get(ResponseInterface::class)->willReturn(function () {
+ return $this->response;
+ });
+ }
- $handler = $factory($container->reveal());
+ public function testFactoryCreatesInstanceWithoutRendererIfRendererServiceIsMissing()
+ {
+ $this->container->has('config')->willReturn(false);
+ $this->container->has(TemplateRendererInterface::class)->willReturn(false);
+ $factory = new NotFoundHandlerFactory();
+ $handler = $factory($this->container->reveal());
$this->assertInstanceOf(NotFoundHandler::class, $handler);
- $this->assertAttributeSame($delegate, 'internalDelegate', $handler);
+ $this->assertAttributeInternalType('callable', 'responseFactory', $handler);
+ $this->assertAttributeEmpty('renderer', $handler);
+ }
+
+ public function testFactoryCreatesInstanceUsingRendererServiceWhenPresent()
+ {
+ $renderer = $this->prophesize(TemplateRendererInterface::class)->reveal();
+ $this->container->has('config')->willReturn(false);
+ $this->container->has(TemplateRendererInterface::class)->willReturn(true);
+ $this->container->get(TemplateRendererInterface::class)->willReturn($renderer);
+ $factory = new NotFoundHandlerFactory();
+
+ $handler = $factory($this->container->reveal());
+ $this->assertAttributeSame($renderer, 'renderer', $handler);
+ }
+
+ public function testFactoryUsesConfigured404TemplateWhenPresent()
+ {
+ $config = [
+ 'zend-expressive' => [
+ 'error_handler' => [
+ 'layout' => 'layout::error',
+ 'template_404' => 'foo::bar',
+ ],
+ ],
+ ];
+ $this->container->has('config')->willReturn(true);
+ $this->container->get('config')->willReturn($config);
+ $this->container->has(TemplateRendererInterface::class)->willReturn(false);
+ $factory = new NotFoundHandlerFactory();
+
+ $handler = $factory($this->container->reveal());
+ $this->assertAttributeEquals(
+ $config['zend-expressive']['error_handler']['layout'],
+ 'layout',
+ $handler
+ );
+ $this->assertAttributeEquals(
+ $config['zend-expressive']['error_handler']['template_404'],
+ 'template',
+ $handler
+ );
}
}
diff --git a/test/Container/RequestHandlerRunnerFactoryTest.php b/test/Container/RequestHandlerRunnerFactoryTest.php
new file mode 100644
index 00000000..1db128f4
--- /dev/null
+++ b/test/Container/RequestHandlerRunnerFactoryTest.php
@@ -0,0 +1,92 @@
+prophesize(ContainerInterface::class);
+ $handler = $this->registerHandlerInContainer($container);
+ $emitter = $this->registerEmitterInContainer($container);
+ $serverRequestFactory = $this->registerServerRequestFactoryInContainer($container);
+ $errorGenerator = $this->registerServerRequestErrorResponseGeneratorInContainer($container);
+
+ $factory = new RequestHandlerRunnerFactory();
+
+ $runner = $factory($container->reveal());
+
+ $this->assertInstanceOf(RequestHandlerRunner::class, $runner);
+ $this->assertAttributeSame($handler, 'handler', $runner);
+ $this->assertAttributeSame($emitter, 'emitter', $runner);
+
+ $this->assertAttributeNotSame($serverRequestFactory, 'serverRequestFactory', $runner);
+ $this->assertAttributeNotSame($errorGenerator, 'serverRequestErrorResponseGenerator', $runner);
+
+ $r = new ReflectionProperty($runner, 'serverRequestFactory');
+ $r->setAccessible(true);
+ $toTest = $r->getValue($runner);
+ $this->assertSame($serverRequestFactory(), $toTest());
+
+ $r = new ReflectionProperty($runner, 'serverRequestErrorResponseGenerator');
+ $r->setAccessible(true);
+ $toTest = $r->getValue($runner);
+ $e = new RuntimeException();
+ $this->assertSame($errorGenerator($e), $toTest($e));
+ }
+
+ public function registerHandlerInContainer($container) : RequestHandlerInterface
+ {
+ $app = $this->prophesize(RequestHandlerInterface::class)->reveal();
+ $container->get(ApplicationPipeline::class)->willReturn($app);
+ return $app;
+ }
+
+ public function registerEmitterInContainer($container) : EmitterInterface
+ {
+ $emitter = $this->prophesize(EmitterInterface::class)->reveal();
+ $container->get(EmitterInterface::class)->willReturn($emitter);
+ return $emitter;
+ }
+
+ public function registerServerRequestFactoryInContainer($container) : callable
+ {
+ $request = $this->prophesize(ServerRequestInterface::class)->reveal();
+ $factory = function () use ($request) {
+ return $request;
+ };
+ $container->get(ServerRequestInterface::class)->willReturn($factory);
+ return $factory;
+ }
+
+ public function registerServerRequestErrorResponseGeneratorInContainer($container) : callable
+ {
+ $response = $this->prophesize(ResponseInterface::class)->reveal();
+ $generator = $this->prophesize(ServerRequestErrorResponseGenerator::class);
+ $generator->__invoke(Argument::type(Throwable::class))->willReturn($response);
+ $container->get(ServerRequestErrorResponseGenerator::class)->willReturn($generator->reveal());
+ return $generator->reveal();
+ }
+}
diff --git a/test/Container/ResponseFactoryFactoryTest.php b/test/Container/ResponseFactoryFactoryTest.php
index 3e90ffd9..3802a300 100644
--- a/test/Container/ResponseFactoryFactoryTest.php
+++ b/test/Container/ResponseFactoryFactoryTest.php
@@ -1,10 +1,12 @@
container = $this->prophesize(ContainerInterface::class)->reveal();
+ $this->factory = new ResponseFactoryFactory();
+
+ $this->autoloadFunctions = spl_autoload_functions();
+ foreach ($this->autoloadFunctions as $autoloader) {
+ spl_autoload_unregister($autoloader);
+ }
+ }
+
+ private function reloadAutoloaders()
+ {
+ foreach ($this->autoloadFunctions as $autoloader) {
+ spl_autoload_register($autoloader);
+ }
+ }
+
+ public function testFactoryRaisesAnExceptionIfDiactorosIsNotLoaded()
+ {
+ $this->expectException(InvalidServiceException::class);
+ $this->expectExceptionMessage('zendframework/zend-diactoros');
+
+ try {
+ ($this->factory)($this->container);
+ } finally {
+ $this->reloadAutoloaders();
+ }
+ }
+}
diff --git a/test/Container/ServerRequestErrorResponseGeneratorFactoryTest.php b/test/Container/ServerRequestErrorResponseGeneratorFactoryTest.php
new file mode 100644
index 00000000..5b4d52dc
--- /dev/null
+++ b/test/Container/ServerRequestErrorResponseGeneratorFactoryTest.php
@@ -0,0 +1,99 @@
+prophesize(ContainerInterface::class);
+ $container->has('config')->willReturn(false);
+ $container->get('config')->shouldNotBeCalled();
+ $container->has(TemplateRendererInterface::class)->willReturn(false);
+ $container->get(TemplateRendererInterface::class)->shouldNotBeCalled();
+
+ $exception = new RuntimeException();
+ $container->get(ResponseInterface::class)->willThrow($exception);
+
+ $factory = new ServerRequestErrorResponseGeneratorFactory();
+
+ $this->expectException(RuntimeException::class);
+ $factory($container->reveal());
+ }
+
+ public function testFactoryCreatesGeneratorWhenOnlyResponseServiceIsPresent()
+ {
+ $container = $this->prophesize(ContainerInterface::class);
+ $container->has('config')->willReturn(false);
+ $container->get('config')->shouldNotBeCalled();
+ $container->has(TemplateRendererInterface::class)->willReturn(false);
+ $container->get(TemplateRendererInterface::class)->shouldNotBeCalled();
+
+ $responseFactory = function () {
+ };
+ $container->get(ResponseInterface::class)->willReturn($responseFactory);
+
+ $factory = new ServerRequestErrorResponseGeneratorFactory();
+
+ $generator = $factory($container->reveal());
+
+ $this->assertAttributeNotSame($responseFactory, 'responseFactory', $generator);
+ $this->assertAttributeInstanceOf(Closure::class, 'responseFactory', $generator);
+ $this->assertAttributeSame(false, 'debug', $generator);
+ $this->assertAttributeEmpty('renderer', $generator);
+ $this->assertAttributeSame(ServerRequestErrorResponseGenerator::TEMPLATE_DEFAULT, 'template', $generator);
+ }
+
+ public function testFactoryCreatesGeneratorUsingConfiguredServices()
+ {
+ $config = [
+ 'debug' => true,
+ 'zend-expressive' => [
+ 'error_handler' => [
+ 'template_error' => 'some::template',
+ ],
+ ],
+ ];
+ $renderer = $this->prophesize(TemplateRendererInterface::class)->reveal();
+
+ $container = $this->prophesize(ContainerInterface::class);
+ $container->has('config')->willReturn(true);
+ $container->get('config')->willReturn($config);
+ $container->has(TemplateRendererInterface::class)->willReturn(true);
+ $container->get(TemplateRendererInterface::class)->willReturn($renderer);
+
+ $responseFactory = function () {
+ };
+ $container->get(ResponseInterface::class)->willReturn($responseFactory);
+
+ $factory = new ServerRequestErrorResponseGeneratorFactory();
+
+ $generator = $factory($container->reveal());
+
+ $this->assertAttributeNotSame($responseFactory, 'responseFactory', $generator);
+ $this->assertAttributeInstanceOf(Closure::class, 'responseFactory', $generator);
+ $this->assertAttributeSame(true, 'debug', $generator);
+ $this->assertAttributeSame($renderer, 'renderer', $generator);
+ $this->assertAttributeSame(
+ $config['zend-expressive']['error_handler']['template_error'],
+ 'template',
+ $generator
+ );
+ }
+}
diff --git a/test/Container/ServerRequestFactoryFactoryTest.php b/test/Container/ServerRequestFactoryFactoryTest.php
new file mode 100644
index 00000000..3978bcfb
--- /dev/null
+++ b/test/Container/ServerRequestFactoryFactoryTest.php
@@ -0,0 +1,47 @@
+prophesize(ContainerInterface::class)->reveal();
+ $factory = new ServerRequestFactoryFactory();
+
+ $generatedFactory = $factory($container);
+
+ $this->assertInternalType('callable', $generatedFactory);
+
+ return $generatedFactory;
+ }
+
+ /**
+ * Some containers do not allow returning generic PHP callables, and will
+ * error when one is returned; one example is Auryn. As such, the factory
+ * cannot simply return a callable referencing the
+ * ServerRequestFactory::fromGlobals method, but must be decorated as a
+ * closure.
+ *
+ * @depends testFactoryReturnsCallable
+ */
+ public function testFactoryIsAClosure(callable $factory)
+ {
+ $this->assertNotSame([ServerRequestFactory::class, 'fromGlobals'], $factory);
+ $this->assertNotSame(ServerRequestFactory::class . '::fromGlobals', $factory);
+ $this->assertInstanceOf(Closure::class, $factory);
+ }
+}
diff --git a/test/Container/ServerRequestFactoryFactoryWithoutDiactorosTest.php b/test/Container/ServerRequestFactoryFactoryWithoutDiactorosTest.php
new file mode 100644
index 00000000..3194d44e
--- /dev/null
+++ b/test/Container/ServerRequestFactoryFactoryWithoutDiactorosTest.php
@@ -0,0 +1,60 @@
+container = $this->prophesize(ContainerInterface::class)->reveal();
+ $this->factory = new ServerRequestFactoryFactory();
+
+ $this->autoloadFunctions = spl_autoload_functions();
+ foreach ($this->autoloadFunctions as $autoloader) {
+ spl_autoload_unregister($autoloader);
+ }
+ }
+
+ private function reloadAutoloaders()
+ {
+ foreach ($this->autoloadFunctions as $autoloader) {
+ spl_autoload_register($autoloader);
+ }
+ }
+
+ public function testFactoryRaisesAnExceptionIfDiactorosIsNotLoaded()
+ {
+ $this->expectException(InvalidServiceException::class);
+ $this->expectExceptionMessage('zendframework/zend-diactoros');
+
+ try {
+ ($this->factory)($this->container);
+ } finally {
+ $this->reloadAutoloaders();
+ }
+ }
+}
diff --git a/test/Container/StreamFactoryFactoryTest.php b/test/Container/StreamFactoryFactoryTest.php
index e0466f39..62b70895 100644
--- a/test/Container/StreamFactoryFactoryTest.php
+++ b/test/Container/StreamFactoryFactoryTest.php
@@ -1,10 +1,12 @@
container = $this->prophesize(ContainerInterface::class)->reveal();
+ $this->factory = new StreamFactoryFactory();
+
+ foreach (spl_autoload_functions() as $autoloader) {
+ if (! is_array($autoloader)) {
+ continue;
+ }
+
+ $context = $autoloader[0];
+
+ if (! is_object($context)
+ || ! preg_match('/^Composer.*?ClassLoader$/', get_class($context))
+ ) {
+ continue;
+ }
+
+ $this->autoloadFunctions[] = $autoloader;
+
+ spl_autoload_unregister($autoloader);
+ }
+ }
+
+ public function tearDown()
+ {
+ $this->reloadAutoloaders();
+ }
+
+ public function reloadAutoloaders()
+ {
+ foreach ($this->autoloadFunctions as $autoloader) {
+ spl_autoload_register($autoloader);
+ }
+ $this->autoloadFunctions = [];
+ }
+
+ public function testFactoryRaisesAnExceptionIfDiactorosIsNotLoaded()
+ {
+ $e = null;
+
+ try {
+ ($this->factory)($this->container);
+ } catch (Throwable $e) {
+ }
+
+ $this->reloadAutoloaders();
+
+ $this->assertInstanceOf(InvalidServiceException::class, $e);
+ $this->assertContains('zendframework/zend-diactoros', $e->getMessage());
+ }
+}
diff --git a/test/Application/TestAsset/CallableInteropMiddleware.php b/test/Container/TestAsset/CallableInteropMiddleware.php
similarity index 60%
rename from test/Application/TestAsset/CallableInteropMiddleware.php
rename to test/Container/TestAsset/CallableInteropMiddleware.php
index eaa8b34d..4f27b3cd 100644
--- a/test/Application/TestAsset/CallableInteropMiddleware.php
+++ b/test/Container/TestAsset/CallableInteropMiddleware.php
@@ -1,17 +1,19 @@
emitter = new EmitterStack();
- }
-
- public function testIsAnSplStack()
- {
- $this->assertInstanceOf(SplStack::class, $this->emitter);
- }
-
- public function testIsAnEmitterImplementation()
- {
- $this->assertInstanceOf(EmitterInterface::class, $this->emitter);
- }
-
- public function nonEmitterValues()
- {
- return [
- 'null' => [null],
- 'true' => [true],
- 'false' => [false],
- 'zero' => [0],
- 'int' => [1],
- 'zero-float' => [0.0],
- 'float' => [1.1],
- 'string' => ['emitter'],
- 'array' => [[$this->prophesize(EmitterInterface::class)->reveal()]],
- 'object' => [(object) []],
- ];
- }
-
- /**
- * @dataProvider nonEmitterValues
- *
- * @param mixed $value
- */
- public function testCannotPushNonEmitterToStack($value)
- {
- $this->expectException(InvalidArgumentException::class);
- $this->emitter->push($value);
- }
-
- /**
- * @dataProvider nonEmitterValues
- *
- * @param mixed $value
- */
- public function testCannotUnshiftNonEmitterToStack($value)
- {
- $this->expectException(InvalidArgumentException::class);
- $this->emitter->unshift($value);
- }
-
- /**
- * @dataProvider nonEmitterValues
- *
- * @param mixed $value
- */
- public function testCannotSetNonEmitterToSpecificIndex($value)
- {
- $this->expectException(InvalidArgumentException::class);
- $this->emitter->offsetSet(0, $value);
- }
-
- public function testOffsetSetReplacesExistingValue()
- {
- $first = $this->prophesize(EmitterInterface::class);
- $replacement = $this->prophesize(EmitterInterface::class);
- $this->emitter->push($first->reveal());
- $this->emitter->offsetSet(0, $replacement->reveal());
- $this->assertSame($replacement->reveal(), $this->emitter->pop());
- }
-
- public function testUnshiftAddsNewEmitter()
- {
- $first = $this->prophesize(EmitterInterface::class);
- $second = $this->prophesize(EmitterInterface::class);
- $this->emitter->push($first->reveal());
- $this->emitter->unshift($second->reveal());
- $this->assertSame($first->reveal(), $this->emitter->pop());
- }
-
- public function testEmitLoopsThroughEmittersUntilOneReturnsNonFalseValue()
- {
- $first = $this->prophesize(EmitterInterface::class);
- $first->emit()->shouldNotBeCalled();
-
- $second = $this->prophesize(EmitterInterface::class);
- $second->emit(Argument::type(ResponseInterface::class))
- ->willReturn(null);
-
- $third = $this->prophesize(EmitterInterface::class);
- $third->emit(Argument::type(ResponseInterface::class))
- ->willReturn(false);
-
- $this->emitter->push($first->reveal());
- $this->emitter->push($second->reveal());
- $this->emitter->push($third->reveal());
-
- $response = $this->prophesize(ResponseInterface::class);
-
- $this->assertNull($this->emitter->emit($response->reveal()));
- }
-
- public function testEmitReturnsFalseIfLastEmmitterReturnsFalse()
- {
- $first = $this->prophesize(EmitterInterface::class);
- $first->emit(Argument::type(ResponseInterface::class))
- ->willReturn(false);
-
- $this->emitter->push($first->reveal());
-
- $response = $this->prophesize(ResponseInterface::class);
-
- $this->assertFalse($this->emitter->emit($response->reveal()));
- }
-
- public function testEmitReturnsFalseIfNoEmittersAreComposed()
- {
- $response = $this->prophesize(ResponseInterface::class);
-
- $this->assertFalse($this->emitter->emit($response->reveal()));
- }
-}
diff --git a/test/ExceptionTest.php b/test/ExceptionTest.php
new file mode 100644
index 00000000..2aaa7ae0
--- /dev/null
+++ b/test/ExceptionTest.php
@@ -0,0 +1,55 @@
+ [$namespace . $class];
+ }
+ }
+
+ /**
+ * @dataProvider exception
+ */
+ public function testExceptionIsInstanceOfExceptionInterface(string $exception) : void
+ {
+ $this->assertContains('Exception', $exception);
+ $this->assertTrue(is_a($exception, ExceptionInterface::class, true));
+ }
+
+ public function containerException() : Generator
+ {
+ yield InvalidMiddlewareException::class => [InvalidMiddlewareException::class];
+ yield MissingDependencyException::class => [MissingDependencyException::class];
+ }
+
+ /**
+ * @dataProvider containerException
+ */
+ public function testExceptionIsInstanceOfContainerExceptionInterface(string $exception) : void
+ {
+ $this->assertTrue(is_a($exception, ContainerExceptionInterface::class, true));
+ }
+}
diff --git a/test/Handler/NotFoundHandlerTest.php b/test/Handler/NotFoundHandlerTest.php
index cd868057..e3a4a141 100644
--- a/test/Handler/NotFoundHandlerTest.php
+++ b/test/Handler/NotFoundHandlerTest.php
@@ -1,10 +1,12 @@
request = $this->prophesize(ServerRequestInterface::class);
$this->response = $this->prophesize(ResponseInterface::class);
+ $this->responseFactory = function () {
+ return $this->response->reveal();
+ };
+ }
+
+ public function testImplementsRequesthandler()
+ {
+ $handler = new NotFoundHandler($this->responseFactory);
+ $this->assertInstanceOf(RequestHandlerInterface::class, $handler);
}
public function testConstructorDoesNotRequireARenderer()
{
- $handler = new NotFoundHandler($this->response->reveal());
+ $handler = new NotFoundHandler($this->responseFactory);
$this->assertInstanceOf(NotFoundHandler::class, $handler);
- $this->assertAttributeSame($this->response->reveal(), 'responsePrototype', $handler);
}
public function testConstructorCanAcceptRendererAndTemplate()
@@ -40,7 +58,7 @@ public function testConstructorCanAcceptRendererAndTemplate()
$template = 'foo::bar';
$layout = 'layout::error';
- $handler = new NotFoundHandler($this->response->reveal(), $renderer, $template, $layout);
+ $handler = new NotFoundHandler($this->responseFactory, $renderer, $template, $layout);
$this->assertInstanceOf(NotFoundHandler::class, $handler);
$this->assertAttributeSame($renderer, 'renderer', $handler);
@@ -59,9 +77,9 @@ public function testRendersDefault404ResponseWhenNoRendererPresent()
$this->response->withStatus(StatusCode::STATUS_NOT_FOUND)->will([$this->response, 'reveal']);
$this->response->getBody()->will([$stream, 'reveal']);
- $handler = new NotFoundHandler($this->response->reveal());
+ $handler = new NotFoundHandler($this->responseFactory);
- $response = $handler->process($request->reveal());
+ $response = $handler->handle($request->reveal());
$this->assertSame($this->response->reveal(), $response);
}
@@ -87,9 +105,9 @@ public function testUsesRendererToGenerateResponseContentsWhenPresent()
$this->response->withStatus(StatusCode::STATUS_NOT_FOUND)->will([$this->response, 'reveal']);
$this->response->getBody()->will([$stream, 'reveal']);
- $handler = new NotFoundHandler($this->response->reveal(), $renderer->reveal());
+ $handler = new NotFoundHandler($this->responseFactory, $renderer->reveal());
- $response = $handler->process($request);
+ $response = $handler->handle($request);
$this->assertSame($this->response->reveal(), $response);
}
diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php
deleted file mode 100644
index 89e44d18..00000000
--- a/test/IntegrationTest.php
+++ /dev/null
@@ -1,116 +0,0 @@
-response = null;
- $this->errorHandler = null;
- }
-
- public function tearDown()
- {
- if ($this->errorHandler) {
- set_error_handler($this->errorHandler);
- $this->errorHandler = null;
- }
- }
-
- public function getEmitter()
- {
- $self = $this;
- $emitter = $this->prophesize(EmitterInterface::class);
- $emitter
- ->emit(Argument::type(ResponseInterface::class))
- ->will(function ($args) use ($self) {
- $response = array_shift($args);
- $self->response = $response;
- return null;
- })
- ->shouldBeCalled();
- return $emitter->reveal();
- }
-
- public function testDefaultFinalHandlerCanEmitA404WhenNoMiddlewareMatches()
- {
- $app = new Application(new FastRouteRouter(), null, null, $this->getEmitter());
- $request = new ServerRequest([], [], 'https://example.com/foo', 'GET');
- $response = new Response();
-
- $app->run($request, $response);
-
- $this->assertInstanceOf(ResponseInterface::class, $this->response);
- $this->assertEquals(StatusCode::STATUS_NOT_FOUND, $this->response->getStatusCode());
- }
-
- public function testInjectedFinalHandlerCanEmitA404WhenNoMiddlewareMatches()
- {
- $request = new ServerRequest([], [], 'https://example.com/foo', 'GET');
- $response = new Response();
- $delegate = new NotFoundDelegate($response);
- $app = new Application(new FastRouteRouter(), null, $delegate, $this->getEmitter());
-
- $app->run($request, $response);
-
- $this->assertInstanceOf(ResponseInterface::class, $this->response);
- $this->assertEquals(StatusCode::STATUS_NOT_FOUND, $this->response->getStatusCode());
- }
-
- public function testCallableClassInteropMiddlewareNotRegisteredWithContainerCanBeComposedSuccessfully()
- {
- $response = new Response();
- $routedMiddleware = $this->prophesize(MiddlewareInterface::class);
- $routedMiddleware
- ->process(
- Argument::type(ServerRequestInterface::class),
- Argument::type(DelegateInterface::class)
- )
- ->willReturn($response);
-
- $container = $this->prophesize(ContainerInterface::class);
- $container->has('RoutedMiddleware')->willReturn(true);
- $container->get('RoutedMiddleware')->will([$routedMiddleware, 'reveal']);
- $container->has(TestAsset\CallableInteropMiddleware::class)->willReturn(false);
-
- $delegate = new NotFoundDelegate($response);
- $app = new Application(new FastRouteRouter(), $container->reveal(), $delegate, $this->getEmitter());
-
- $app->pipe(TestAsset\CallableInteropMiddleware::class);
- $app->get('/', 'RoutedMiddleware');
-
- $request = new ServerRequest([], [], 'https://example.com/foo', 'GET');
- $app->run($request, new Response());
-
- $this->assertInstanceOf(ResponseInterface::class, $this->response);
- $this->assertTrue($this->response->hasHeader('X-Callable-Interop-Middleware'));
- $this->assertEquals(
- TestAsset\CallableInteropMiddleware::class,
- $this->response->getHeaderLine('X-Callable-Interop-Middleware')
- );
- }
-}
diff --git a/test/Middleware/ErrorResponseGeneratorTest.php b/test/Middleware/ErrorResponseGeneratorTest.php
index f0b8a0dc..eb8381a4 100644
--- a/test/Middleware/ErrorResponseGeneratorTest.php
+++ b/test/Middleware/ErrorResponseGeneratorTest.php
@@ -1,10 +1,12 @@
false];
- set_error_handler(function ($errno, $errstr) use ($test) {
- $test->message = $errstr;
- return true;
- }, E_USER_DEPRECATED);
-
- $middleware = new ImplicitHeadMiddleware();
- restore_error_handler();
-
- $this->assertInstanceOf(BaseImplicitHeadMiddleware::class, $middleware);
- $this->assertInternalType('string', $test->message);
- $this->assertContains('deprecated starting with zend-expressive 2.2', $test->message);
- }
-}
diff --git a/test/Middleware/ImplicitOptionsMiddlewareTest.php b/test/Middleware/ImplicitOptionsMiddlewareTest.php
deleted file mode 100644
index f138ac19..00000000
--- a/test/Middleware/ImplicitOptionsMiddlewareTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
- false];
- set_error_handler(function ($errno, $errstr) use ($test) {
- $test->message = $errstr;
- return true;
- }, E_USER_DEPRECATED);
-
- $middleware = new ImplicitOptionsMiddleware();
- restore_error_handler();
-
- $this->assertInstanceOf(BaseImplicitOptionsMiddleware::class, $middleware);
- $this->assertInternalType('string', $test->message);
- $this->assertContains('deprecated starting with zend-expressive 2.2', $test->message);
- }
-}
diff --git a/test/Middleware/LazyLoadingMiddlewareTest.php b/test/Middleware/LazyLoadingMiddlewareTest.php
index 34755907..92635106 100644
--- a/test/Middleware/LazyLoadingMiddlewareTest.php
+++ b/test/Middleware/LazyLoadingMiddlewareTest.php
@@ -1,135 +1,76 @@
container = $this->prophesize(ContainerInterface::class);
- $this->response = $this->prophesize(ResponseInterface::class);
+ $this->container = $this->prophesize(MiddlewareContainer::class);
$this->request = $this->prophesize(ServerRequestInterface::class);
- $this->delegate = $this->prophesize(DelegateInterface::class);
+ $this->handler = $this->prophesize(RequestHandlerInterface::class);
}
public function buildLazyLoadingMiddleware($middlewareName)
{
return new LazyLoadingMiddleware(
$this->container->reveal(),
- $this->response->reveal(),
$middlewareName
);
}
- public function testInvokesInteropMiddlewarePulledFromContainer()
+ public function testProcessesMiddlewarePulledFromContainer()
{
- $expected = $this->prophesize(ResponseInterface::class)->reveal();
-
- $middleware = $this->prophesize(ServerMiddlewareInterface::class);
+ $response = $this->prophesize(ResponseInterface::class)->reveal();
+ $middleware = $this->prophesize(MiddlewareInterface::class);
$middleware
->process(
$this->request->reveal(),
- $this->delegate->reveal()
- )
- ->willReturn($expected);
-
- $this->container->get('middleware')->will([$middleware, 'reveal']);
-
- $lazyLoadingMiddleware = $this->buildLazyLoadingMiddleware('middleware');
- $this->assertSame(
- $expected,
- $lazyLoadingMiddleware->process($this->request->reveal(), $this->delegate->reveal())
- );
- }
-
- public function testInvokesDuckTypedInteropMiddlewarePulledFromContainer()
- {
- $expected = $this->prophesize(ResponseInterface::class)->reveal();
-
- $middleware = function ($request, DelegateInterface $delegate) use ($expected) {
- return $expected;
- };
+ $this->handler->reveal()
+ )->willReturn($response);
- $this->container->get('middleware')->willReturn($middleware);
+ $this->container->get('foo')->will([$middleware, 'reveal']);
- $lazyLoadingMiddleware = $this->buildLazyLoadingMiddleware('middleware');
+ $lazyloader = $this->buildLazyLoadingMiddleware('foo');
$this->assertSame(
- $expected,
- $lazyLoadingMiddleware->process($this->request->reveal(), $this->delegate->reveal())
+ $response,
+ $lazyloader->process($this->request->reveal(), $this->handler->reveal())
);
}
- public function testInvokesDoublePassMiddlewarePulledFromContainerUsingResponsePrototype()
- {
- $expected = $this->prophesize(ResponseInterface::class)->reveal();
-
- $middleware = function ($request, $response, callable $next) use ($expected) {
- return $expected;
- };
-
- $this->container->get('middleware')->willReturn($middleware);
-
- $lazyLoadingMiddleware = $this->buildLazyLoadingMiddleware('middleware');
- $this->assertSame(
- $expected,
- $lazyLoadingMiddleware->process($this->request->reveal(), $this->delegate->reveal())
- );
- }
-
- public function invalidMiddleware()
- {
- return [
- 'null' => [null],
- 'true' => [true],
- 'false' => [false],
- 'zero' => [0],
- 'int' => [1],
- 'zero-float' => [0.0],
- 'float' => [1.1],
- 'non-invokable-string' => ['not-real-middleware'],
- 'non-invokable-array' => [['not', 'real', 'middleware']],
- 'non-invokable-object' => [(object) ['middleware' => false]],
- ];
- }
-
- /**
- * @dataProvider invalidMiddleware
- *
- * @param mixed $middleware
- */
- public function testRaisesExceptionIfMiddlewarePulledFromContainerIsInvalid($middleware)
+ public function testDoesNotCatchContainerExceptions()
{
- $this->container->get('middleware')->willReturn($middleware);
- $lazyLoadingMiddleware = $this->buildLazyLoadingMiddleware('middleware');
+ $exception = new InvalidMiddlewareException();
+ $this->container->get('foo')->willThrow($exception);
+ $lazyloader = $this->buildLazyLoadingMiddleware('foo');
$this->expectException(InvalidMiddlewareException::class);
- $lazyLoadingMiddleware->process($this->request->reveal(), $this->delegate->reveal());
+ $lazyloader->process($this->request->reveal(), $this->handler->reveal());
}
}
diff --git a/test/Middleware/NotFoundHandlerTest.php b/test/Middleware/NotFoundHandlerTest.php
deleted file mode 100644
index 8511b3c8..00000000
--- a/test/Middleware/NotFoundHandlerTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-internal = $this->prophesize(NotFoundDelegate::class);
- $this->request = $this->prophesize(ServerRequestInterface::class);
-
- $this->delegate = $this->prophesize(DelegateInterface::class);
- $this->delegate->process(Argument::type(ServerRequestInterface::class))->shouldNotBeCalled();
- }
-
- public function testImplementsInteropMiddleware()
- {
- $handler = new NotFoundHandler($this->internal->reveal());
- $this->assertInstanceOf(MiddlewareInterface::class, $handler);
- }
-
- public function testProxiesToInternalDelegate()
- {
- $this->internal
- ->process(Argument::that([$this->request, 'reveal']))
- ->willReturn('CONTENT');
-
- $handler = new NotFoundHandler($this->internal->reveal());
- $this->assertEquals('CONTENT', $handler->process($this->request->reveal(), $this->delegate->reveal()));
- }
-}
diff --git a/test/Middleware/WhoopsErrorResponseGeneratorTest.php b/test/Middleware/WhoopsErrorResponseGeneratorTest.php
index 125cf166..d0870ede 100644
--- a/test/Middleware/WhoopsErrorResponseGeneratorTest.php
+++ b/test/Middleware/WhoopsErrorResponseGeneratorTest.php
@@ -1,10 +1,12 @@
originContainer = $this->prophesize(ContainerInterface::class);
+ $this->container = new MiddlewareContainer($this->originContainer->reveal());
+ }
+
+ public function testHasReturnsTrueIfOriginContainerHasService()
+ {
+ $this->originContainer->has('foo')->willReturn(true);
+ $this->assertTrue($this->container->has('foo'));
+ }
+
+ public function testHasReturnsTrueIfOriginContainerDoesNotHaveServiceButClassExists()
+ {
+ $this->originContainer->has(__CLASS__)->willReturn(false);
+ $this->assertTrue($this->container->has(__CLASS__));
+ }
+
+ public function testHasReturnsFalseIfOriginContainerDoesNotHaveServiceAndClassDoesNotExist()
+ {
+ $this->originContainer->has('not-a-class')->willReturn(false);
+ $this->assertFalse($this->container->has('not-a-class'));
+ }
+
+ public function testGetRaisesExceptionIfServiceIsUnknown()
+ {
+ $this->originContainer->has('not-a-service')->willReturn(false);
+
+ $this->expectException(Exception\MissingDependencyException::class);
+ $this->container->get('not-a-service');
+ }
+
+ public function testGetRaisesExceptionIfServiceSpecifiedDoesNotImplementMiddlewareInterface()
+ {
+ $this->originContainer->has(__CLASS__)->willReturn(true);
+ $this->originContainer->get(__CLASS__)->willReturn($this);
+
+ $this->expectException(Exception\InvalidMiddlewareException::class);
+ $this->container->get(__CLASS__);
+ }
+
+ public function testGetRaisesExceptionIfClassSpecifiedDoesNotImplementMiddlewareInterface()
+ {
+ $this->originContainer->has(__CLASS__)->willReturn(false);
+ $this->originContainer->get(__CLASS__)->shouldNotBeCalled();
+
+ $this->expectException(Exception\InvalidMiddlewareException::class);
+ $this->container->get(__CLASS__);
+ }
+
+ public function testGetReturnsServiceFromOriginContainer()
+ {
+ $middleware = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $this->originContainer->has('middleware-service')->willReturn(true);
+ $this->originContainer->get('middleware-service')->willReturn($middleware);
+
+ $this->assertSame($middleware, $this->container->get('middleware-service'));
+ }
+
+ public function testGetReturnsInstantiatedClass()
+ {
+ $this->originContainer->has(DispatchMiddleware::class)->willReturn(false);
+ $this->originContainer->get(DispatchMiddleware::class)->shouldNotBeCalled();
+
+ $middleware = $this->container->get(DispatchMiddleware::class);
+ $this->assertInstanceOf(DispatchMiddleware::class, $middleware);
+ }
+
+ public function testGetWillDecorateARequestHandlerAsMiddleware()
+ {
+ $handler = $this->prophesize(RequestHandlerInterface::class)->reveal();
+
+ $this->originContainer->has('AHandlerNotMiddleware')->willReturn(true);
+ $this->originContainer->get('AHandlerNotMiddleware')->willReturn($handler);
+
+ $middleware = $this->container->get('AHandlerNotMiddleware');
+
+ // Test that we get back middleware decorating the handler
+ $this->assertInstanceOf(RequestHandlerMiddleware::class, $middleware);
+ $this->assertAttributeSame($handler, 'handler', $middleware);
+ }
+}
diff --git a/test/MiddlewareFactoryTest.php b/test/MiddlewareFactoryTest.php
new file mode 100644
index 00000000..21ce26ab
--- /dev/null
+++ b/test/MiddlewareFactoryTest.php
@@ -0,0 +1,215 @@
+container = $this->prophesize(MiddlewareContainer::class);
+ $this->factory = new MiddlewareFactory($this->container->reveal());
+ }
+
+ public function assertLazyLoadingMiddleware(string $expectedMiddlewareName, MiddlewareInterface $middleware)
+ {
+ $this->assertInstanceOf(LazyLoadingMiddleware::class, $middleware);
+ $this->assertAttributeSame($this->container->reveal(), 'container', $middleware);
+ $this->assertAttributeSame($expectedMiddlewareName, 'middlewareName', $middleware);
+ }
+
+ public function assertCallableMiddleware(callable $expectedCallable, MiddlewareInterface $middleware)
+ {
+ $this->assertInstanceOf(CallableMiddlewareDecorator::class, $middleware);
+ $this->assertAttributeSame($expectedCallable, 'middleware', $middleware);
+ }
+
+ public function assertPipeline(array $expectedPipeline, MiddlewareInterface $middleware)
+ {
+ $this->assertInstanceOf(MiddlewarePipe::class, $middleware);
+ $pipeline = $this->reflectPipeline($middleware);
+ $this->assertSame($expectedPipeline, $pipeline);
+ }
+
+ public function reflectPipeline(MiddlewarePipe $pipeline) : array
+ {
+ $r = new ReflectionProperty($pipeline, 'pipeline');
+ $r->setAccessible(true);
+ return iterator_to_array($r->getValue($pipeline));
+ }
+
+ public function testCallableDecoratesCallableMiddleware()
+ {
+ $callable = function ($request, $handler) {
+ };
+
+ $middleware = $this->factory->callable($callable);
+ $this->assertCallableMiddleware($callable, $middleware);
+ }
+
+ public function testLazyLoadingMiddlewareDecoratesMiddlewareServiceName()
+ {
+ $middleware = $this->factory->lazy('service');
+ $this->assertLazyLoadingMiddleware('service', $middleware);
+ }
+
+ public function testPrepareReturnsMiddlewareImplementationsVerbatim()
+ {
+ $middleware = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $this->assertSame($middleware, $this->factory->prepare($middleware));
+ }
+
+ public function testPrepareDecoratesCallables()
+ {
+ $callable = function ($request, $handler) {
+ };
+
+ $middleware = $this->factory->prepare($callable);
+ $this->assertInstanceOf(CallableMiddlewareDecorator::class, $middleware);
+ $this->assertAttributeSame($callable, 'middleware', $middleware);
+ }
+
+ public function testPrepareDecoratesServiceNamesAsLazyLoadingMiddleware()
+ {
+ $middleware = $this->factory->prepare('service');
+ $this->assertInstanceOf(LazyLoadingMiddleware::class, $middleware);
+ $this->assertAttributeSame('service', 'middlewareName', $middleware);
+ $this->assertAttributeSame($this->container->reveal(), 'container', $middleware);
+ }
+
+ public function testPrepareDecoratesArraysAsMiddlewarePipes()
+ {
+ $middleware1 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware2 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware3 = $this->prophesize(MiddlewareInterface::class)->reveal();
+
+ $middleware = $this->factory->prepare([$middleware1, $middleware2, $middleware3]);
+ $this->assertPipeline([$middleware1, $middleware2, $middleware3], $middleware);
+ }
+
+ public function invalidMiddlewareTypes() : iterable
+ {
+ yield 'null' => [null];
+ yield 'false' => [false];
+ yield 'true' => [true];
+ yield 'zero' => [0];
+ yield 'int' => [1];
+ yield 'zero-float' => [0.0];
+ yield 'float' => [1.1];
+ yield 'object' => [(object) ['foo' => 'bar']];
+ }
+
+ /**
+ * @dataProvider invalidMiddlewareTypes
+ */
+ public function testPrepareRaisesExceptionForTypesItDoesNotUnderstand($middleware)
+ {
+ $this->expectException(Exception\InvalidMiddlewareException::class);
+ $this->factory->prepare($middleware);
+ }
+
+ public function testPipelineAcceptsMultipleArguments()
+ {
+ $middleware1 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware2 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware3 = $this->prophesize(MiddlewareInterface::class)->reveal();
+
+ $middleware = $this->factory->pipeline($middleware1, $middleware2, $middleware3);
+ $this->assertPipeline([$middleware1, $middleware2, $middleware3], $middleware);
+ }
+
+ public function testPipelineAcceptsASingleArrayArgument()
+ {
+ $middleware1 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware2 = $this->prophesize(MiddlewareInterface::class)->reveal();
+ $middleware3 = $this->prophesize(MiddlewareInterface::class)->reveal();
+
+ $middleware = $this->factory->pipeline([$middleware1, $middleware2, $middleware3]);
+ $this->assertPipeline([$middleware1, $middleware2, $middleware3], $middleware);
+ }
+
+ public function validPrepareTypes()
+ {
+ yield 'service' => ['service', 'assertLazyLoadingMiddleware', 'service'];
+
+ $callable = function ($request, $handler) {
+ };
+ yield 'callable' => [$callable, 'assertCallableMiddleware', $callable];
+
+ $middleware = new DispatchMiddleware();
+ yield 'instance' => [$middleware, 'assertSame', $middleware];
+ }
+
+ /**
+ * @dataProvider validPrepareTypes
+ * @param string|callable|MiddlewareInterface $middleware
+ * @param mixed $expected Expected type or value for use with assertion
+ */
+ public function testPipelineAllowsAnyTypeSupportedByPrepare(
+ $middleware,
+ string $assertion,
+ $expected
+ ) {
+ $pipeline = $this->factory->pipeline($middleware);
+ $this->assertInstanceOf(MiddlewarePipe::class, $pipeline);
+
+ $r = new ReflectionProperty($pipeline, 'pipeline');
+ $r->setAccessible(true);
+ $values = iterator_to_array($r->getValue($pipeline));
+ $received = array_shift($values);
+
+ $this->{$assertion}($expected, $received);
+ }
+
+ public function testPipelineAllowsPipingArraysOfMiddlewareAndCastsThemToInternalPipelines()
+ {
+ $callable = function ($request, $handler) {
+ };
+ $middleware = new DispatchMiddleware();
+
+ $internalPipeline = [$callable, $middleware];
+
+ $pipeline = $this->factory->pipeline($internalPipeline);
+
+ $this->assertInstanceOf(MiddlewarePipe::class, $pipeline);
+ $received = $this->reflectPipeline($pipeline);
+ $this->assertCount(2, $received);
+ $this->assertCallableMiddleware($callable, $received[0]);
+ $this->assertSame($middleware, $received[1]);
+ }
+
+ public function testPrepareDecoratesRequestHandlersAsMiddleware()
+ {
+ $handler = $this->prophesize(RequestHandlerInterface::class)->reveal();
+ $middleware = $this->factory->prepare($handler);
+ $this->assertInstanceOf(RequestHandlerMiddleware::class, $middleware);
+ $this->assertAttributeSame($handler, 'handler', $middleware);
+ }
+
+ public function testHandlerDecoratesRequestHandlersAsMiddleware()
+ {
+ $handler = $this->prophesize(RequestHandlerInterface::class)->reveal();
+ $middleware = $this->factory->handler($handler);
+ $this->assertInstanceOf(RequestHandlerMiddleware::class, $middleware);
+ $this->assertAttributeSame($handler, 'handler', $middleware);
+ }
+}
diff --git a/test/Response/ServerRequestErrorResponseGeneratorTest.php b/test/Response/ServerRequestErrorResponseGeneratorTest.php
new file mode 100644
index 00000000..a1a5edb1
--- /dev/null
+++ b/test/Response/ServerRequestErrorResponseGeneratorTest.php
@@ -0,0 +1,120 @@
+response = $this->prophesize(ResponseInterface::class);
+ $this->responseFactory = function () {
+ return $this->response->reveal();
+ };
+
+ $this->renderer = $this->prophesize(TemplateRendererInterface::class);
+ }
+
+ public function testPreparesTemplatedResponseWhenRendererPresent()
+ {
+ $stream = $this->prophesize(StreamInterface::class);
+ $stream->write('data from template')->shouldBeCalled();
+
+ $this->response->withStatus(422)->will([$this->response, 'reveal']);
+ $this->response->getBody()->will([$stream, 'reveal']);
+ $this->response->getStatusCode()->willReturn(422);
+ $this->response->getReasonPhrase()->willReturn('Unexpected entity');
+
+ $template = 'some::template';
+ $e = new RuntimeException('This is the exception message', 422);
+ $this->renderer
+ ->render($template, [
+ 'response' => $this->response->reveal(),
+ 'status' => 422,
+ 'reason' => 'Unexpected entity',
+ 'error' => $e,
+ ])
+ ->willReturn('data from template');
+
+ $generator = new ServerRequestErrorResponseGenerator(
+ $this->responseFactory,
+ true,
+ $this->renderer->reveal(),
+ $template
+ );
+
+ $this->assertSame($this->response->reveal(), $generator($e));
+ }
+
+ public function testPreparesResponseWithDefaultMessageOnlyWhenNoRendererPresentAndNotInDebugMode()
+ {
+ $stream = $this->prophesize(StreamInterface::class);
+ $stream->write('An unexpected error occurred')->shouldBeCalled();
+
+ $this->response->withStatus(422)->will([$this->response, 'reveal']);
+ $this->response->getBody()->will([$stream, 'reveal']);
+ $this->response->getStatusCode()->shouldNotBeCalled();
+ $this->response->getReasonPhrase()->shouldNotBeCalled();
+
+ $e = new RuntimeException('This is the exception message', 422);
+
+ $generator = new ServerRequestErrorResponseGenerator($this->responseFactory);
+
+ $this->assertSame($this->response->reveal(), $generator($e));
+ }
+
+ public function testPreparesResponseWithDefaultMessageAndStackTraceWhenNoRendererPresentAndInDebugMode()
+ {
+ $stream = $this->prophesize(StreamInterface::class);
+ $stream
+ ->write(Argument::that(function ($message) {
+ if (! preg_match('/^An unexpected error occurred; stack trace:/', $message)) {
+ echo "Failed first assertion: $message\n";
+ return false;
+ }
+ if (false === strpos($message, 'Stack Trace:')) {
+ echo "Failed second assertion: $message\n";
+ return false;
+ }
+ return $message;
+ }))
+ ->shouldBeCalled();
+
+ $this->response->withStatus(422)->will([$this->response, 'reveal']);
+ $this->response->getBody()->will([$stream, 'reveal']);
+ $this->response->getStatusCode()->shouldNotBeCalled();
+ $this->response->getReasonPhrase()->shouldNotBeCalled();
+
+ $e = new RuntimeException('This is the exception message', 422);
+
+ $generator = new ServerRequestErrorResponseGenerator($this->responseFactory, true);
+
+ $this->assertSame($this->response->reveal(), $generator($e));
+ }
+}
diff --git a/test/RouteResultTrait.php b/test/RouteResultTrait.php
deleted file mode 100644
index c0c48a5c..00000000
--- a/test/RouteResultTrait.php
+++ /dev/null
@@ -1,28 +0,0 @@
-prophesize(Route::class);
- $route->getMiddleware()->willReturn($middleware);
- $route->getPath()->willReturn($name);
- $route->getName()->willReturn(null);
-
- return RouteResult::fromRoute($route->reveal(), $params);
- }
-}
diff --git a/test/Router/IntegrationTest.php b/test/Router/IntegrationTest.php
index 950cc57f..21439e9b 100644
--- a/test/Router/IntegrationTest.php
+++ b/test/Router/IntegrationTest.php
@@ -1,28 +1,38 @@
response = new Response();
+ $this->responseFactory = function () {
+ return $this->response;
+ };
$this->router = $this->prophesize(RouterInterface::class);
$this->container = $this->mockContainerInterface();
$this->disregardDeprecationNotices();
@@ -72,9 +88,21 @@ public function disregardDeprecationNotices()
public function getApplication()
{
+ return $this->createApplicationFromRouter($this->router->reveal());
+ }
+
+ public function createApplicationFromRouter(RouterInterface $router)
+ {
+ $container = new MiddlewareContainer($this->container->reveal());
+ $factory = new MiddlewareFactory($container);
+ $pipeline = new MiddlewarePipe();
+ $routeMiddleware = new RouteMiddleware($router);
+ $runner = $this->prophesize(RequestHandlerRunner::class)->reveal();
return new Application(
- $this->router->reveal(),
- $this->container->reveal()
+ $factory,
+ $pipeline,
+ $routeMiddleware,
+ $runner
);
}
@@ -101,18 +129,20 @@ public function routerAdapters()
*/
private function createApplicationWithGetPost($adapter, $getName = null, $postName = null)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
+ $app->pipe(new MethodNotAllowedMiddleware($this->responseFactory));
- $app->get('/foo', function ($req, $res, $next) {
+ $app->get('/foo', function ($req, $handler) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware GET');
- return $res->withBody($stream);
+ return $this->response->withBody($stream);
}, $getName);
- $app->post('/foo', function ($req, $res, $next) {
+ $app->post('/foo', function ($req, $handler) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware POST');
- return $res->withBody($stream);
+ return $this->response->withBody($stream);
}, $postName);
return $app;
@@ -129,18 +159,20 @@ private function createApplicationWithGetPost($adapter, $getName = null, $postNa
*/
private function createApplicationWithRouteGetPost($adapter, $getName = null, $postName = null)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
+ $app->pipe(new MethodNotAllowedMiddleware($this->responseFactory));
- $app->route('/foo', function ($req, $res, $next) {
+ $app->route('/foo', function ($req, $handler) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware GET');
- return $res->withBody($stream);
+ return $this->response->withBody($stream);
}, ['GET'], $getName);
- $app->route('/foo', function ($req, $res, $next) {
+ $app->route('/foo', function ($req, $handler) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware POST');
- return $res->withBody($stream);
+ return $this->response->withBody($stream);
}, ['POST'], $postName);
return $app;
@@ -154,12 +186,12 @@ private function createApplicationWithRouteGetPost($adapter, $getName = null, $p
public function testRoutingDoesNotMatchMethod($adapter)
{
$app = $this->createApplicationWithGetPost($adapter);
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'DELETE'], [], '/foo', 'DELETE');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertSame(StatusCode::STATUS_METHOD_NOT_ALLOWED, $result->getStatusCode());
$headers = $result->getHeaders();
@@ -177,19 +209,19 @@ public function testRoutingDoesNotMatchMethod($adapter)
public function testRoutingWithSamePathWithoutName($adapter)
{
$app = $this->createApplicationWithGetPost($adapter);
- $app->pipeDispatchMiddleware();
+ $app->pipe(new DispatchMiddleware());
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'POST'], [], '/foo', 'POST');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware POST', (string) $result->getBody());
}
@@ -205,19 +237,20 @@ public function testRoutingWithSamePathWithoutName($adapter)
public function testRoutingWithSamePathWithName($adapter)
{
$app = $this->createApplicationWithGetPost($adapter, 'foo-get', 'foo-post');
- $app->pipeDispatchMiddleware();
+ $app->pipe(new DispatchMiddleware());
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler
+ ->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'POST'], [], '/foo', 'POST');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware POST', (string) $result->getBody());
}
@@ -233,19 +266,19 @@ public function testRoutingWithSamePathWithName($adapter)
public function testRoutingWithSamePathWithRouteWithoutName($adapter)
{
$app = $this->createApplicationWithRouteGetPost($adapter);
- $app->pipeDispatchMiddleware();
+ $app->pipe(new DispatchMiddleware());
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'POST'], [], '/foo', 'POST');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware POST', (string) $result->getBody());
}
@@ -260,19 +293,20 @@ public function testRoutingWithSamePathWithRouteWithoutName($adapter)
public function testRoutingWithSamePathWithRouteWithName($adapter)
{
$app = $this->createApplicationWithRouteGetPost($adapter, 'foo-get', 'foo-post');
- $app->pipeDispatchMiddleware();
+ $app->pipe(new DispatchMiddleware());
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler
+ ->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'POST'], [], '/foo', 'POST');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware POST', (string) $result->getBody());
}
@@ -287,35 +321,41 @@ public function testRoutingWithSamePathWithRouteWithName($adapter)
*/
public function testRoutingWithSamePathWithRouteWithMultipleMethods($adapter)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
- $app->pipeDispatchMiddleware();
-
- $app->route('/foo', function ($req, $res, $next) {
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
+ $app->pipe(new MethodNotAllowedMiddleware($this->responseFactory));
+ $app->pipe(new DispatchMiddleware());
+
+ $response = clone $this->response;
+ $app->route('/foo', function ($req, $handler) use ($response) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware GET, POST');
- return $res->withBody($stream);
+ return $response->withBody($stream);
}, ['GET', 'POST']);
- $app->route('/foo', function ($req, $res, $next) {
+
+ $deleteResponse = clone $this->response;
+ $app->route('/foo', function ($req, $handler) use ($deleteResponse) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware DELETE');
- return $res->withBody($stream);
+ return $deleteResponse->withBody($stream);
}, ['DELETE']);
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler
+ ->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET, POST', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'POST'], [], '/foo', 'POST');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware GET, POST', (string) $result->getBody());
$request = new ServerRequest(['REQUEST_METHOD' => 'DELETE'], [], '/foo', 'DELETE');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware DELETE', (string) $result->getBody());
}
@@ -347,23 +387,27 @@ public function routerAdaptersForHttpMethods()
*/
public function testMatchWithAllHttpMethods($adapter, $method)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
- $app->pipeDispatchMiddleware();
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
+ $app->pipe(new MethodNotAllowedMiddleware($this->responseFactory));
+ $app->pipe(new DispatchMiddleware());
// Add a route with Zend\Expressive\Router\Route::HTTP_METHOD_ANY
- $app->route('/foo', function ($req, $res, $next) {
+ $response = clone $this->response;
+ $app->route('/foo', function ($req, $handler) use ($response) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware');
- return $res->withBody($stream);
+ return $response->withBody($stream);
});
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler
+ ->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
$request = new ServerRequest(['REQUEST_METHOD' => $method], [], '/foo', $method);
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertEquals('Middleware', (string) $result->getBody());
}
@@ -379,26 +423,6 @@ public function allowedMethod()
];
}
- public function notAllowedMethod()
- {
- return [
- 'aura-get' => [AuraRouter::class, RequestMethod::METHOD_GET],
- 'aura-post' => [AuraRouter::class, RequestMethod::METHOD_POST],
- 'aura-put' => [AuraRouter::class, RequestMethod::METHOD_PUT],
- 'aura-delete' => [AuraRouter::class, RequestMethod::METHOD_DELETE],
- 'aura-patch' => [AuraRouter::class, RequestMethod::METHOD_PATCH],
- 'fast-route-post' => [FastRouteRouter::class, RequestMethod::METHOD_POST],
- 'fast-route-put' => [FastRouteRouter::class, RequestMethod::METHOD_PUT],
- 'fast-route-delete' => [FastRouteRouter::class, RequestMethod::METHOD_DELETE],
- 'fast-route-patch' => [FastRouteRouter::class, RequestMethod::METHOD_PATCH],
- 'zf2-get' => [ZendRouter::class, RequestMethod::METHOD_GET],
- 'zf2-post' => [ZendRouter::class, RequestMethod::METHOD_POST],
- 'zf2-put' => [ZendRouter::class, RequestMethod::METHOD_PUT],
- 'zf2-delete' => [ZendRouter::class, RequestMethod::METHOD_DELETE],
- 'zf2-patch' => [ZendRouter::class, RequestMethod::METHOD_PATCH],
- ];
- }
-
/**
* @dataProvider allowedMethod
*
@@ -407,11 +431,14 @@ public function notAllowedMethod()
*/
public function testAllowedMethodsWhenOnlyPutMethodSet($adapter, $method)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
- $app->pipe(new Middleware\ImplicitHeadMiddleware());
- $app->pipe(new Middleware\ImplicitOptionsMiddleware());
- $app->pipeDispatchMiddleware();
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
+ $app->pipe(new ImplicitHeadMiddleware($router, function () {
+ }));
+ $app->pipe(new ImplicitOptionsMiddleware($this->responseFactory));
+ $app->pipe(new MethodNotAllowedMiddleware($this->responseFactory));
+ $app->pipe(new DispatchMiddleware());
// Add a PUT route
$app->put('/foo', function ($req, $res, $next) {
@@ -420,76 +447,19 @@ public function testAllowedMethodsWhenOnlyPutMethodSet($adapter, $method)
return $res->withBody($stream);
});
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler->handle(Argument::type(ServerRequest::class))
->shouldNotBeCalled();
- $request = new ServerRequest(['REQUEST_METHOD' => $method], [], '/foo', $method);
- $result = $app->process($request, $delegate->reveal());
-
- $this->assertEquals(StatusCode::STATUS_OK, $result->getStatusCode());
- $this->assertEquals('', (string) $result->getBody());
- }
-
- /**
- * @dataProvider allowedMethod
- *
- * @param string $adapter
- * @param string $method
- */
- public function testAllowedMethodsWhenNoHttpMethodsSet($adapter, $method)
- {
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
- $app->pipe(new Middleware\ImplicitHeadMiddleware());
- $app->pipe(new Middleware\ImplicitOptionsMiddleware());
- $app->pipeDispatchMiddleware();
-
- // Add a route with empty array - NO HTTP methods
- $app->route('/foo', function ($req, $res, $next) {
- $stream = new Stream('php://temp', 'w+');
- $stream->write('Middleware');
- return $res->withBody($stream);
- }, []);
-
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
- ->willReturn($this->response);
-
$request = new ServerRequest(['REQUEST_METHOD' => $method], [], '/foo', $method);
- $result = $app->process($request, $delegate->reveal());
-
- $this->assertEquals(StatusCode::STATUS_OK, $result->getStatusCode());
- $this->assertEquals('', (string) $result->getBody());
- }
-
- /**
- * @dataProvider notAllowedMethod
- *
- * @param string $adapter
- * @param string $method
- */
- public function testNotAllowedMethodWhenNoHttpMethodsSet($adapter, $method)
- {
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
- $app->pipeDispatchMiddleware();
+ $result = $app->process($request, $handler->reveal());
- // Add a route with empty array - NO HTTP methods
- $app->route('/foo', function ($req, $res, $next) {
- $stream = new Stream('php://temp', 'w+');
- $stream->write('Middleware');
- return $res->withBody($stream);
- }, []);
-
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
- ->shouldNotBeCalled();
-
- $request = new ServerRequest(['REQUEST_METHOD' => $method], [], '/foo', $method);
- $result = $app->process($request, $delegate->reveal());
- $this->assertEquals(StatusCode::STATUS_METHOD_NOT_ALLOWED, $result->getStatusCode());
- $this->assertNotContains('Middleware', (string) $result->getBody());
+ if ($method === RequestMethod::METHOD_OPTIONS) {
+ $this->assertSame(StatusCode::STATUS_OK, $result->getStatusCode());
+ } else {
+ $this->assertSame(StatusCode::STATUS_METHOD_NOT_ALLOWED, $result->getStatusCode());
+ }
+ $this->assertSame('', (string) $result->getBody());
}
/**
@@ -501,22 +471,25 @@ public function testNotAllowedMethodWhenNoHttpMethodsSet($adapter, $method)
*/
public function testWithOnlyRootPathRouteDefinedRoutingToSubPathsShouldDelegate($adapter)
{
- $app = new Application(new $adapter());
- $app->pipeRoutingMiddleware();
+ $router = new $adapter();
+ $app = $this->createApplicationFromRouter($router);
+ $app->pipe(new RouteMiddleware($router));
- $app->route('/', function ($req, $res, $next) {
+ $response = clone $this->response;
+ $app->route('/', function ($req, $handler) use ($response) {
$stream = new Stream('php://temp', 'w+');
$stream->write('Middleware');
- return $res->withBody($stream);
+ return $response->withBody($stream);
}, ['GET']);
- $expected = (new Response())->withStatus(StatusCode::STATUS_NOT_FOUND);
- $delegate = $this->prophesize(DelegateInterface::class);
- $delegate->process(Argument::type(ServerRequest::class))
+ $expected = $this->response->withStatus(StatusCode::STATUS_NOT_FOUND);
+ $handler = $this->prophesize(RequestHandlerInterface::class);
+ $handler
+ ->handle(Argument::type(ServerRequest::class))
->willReturn($expected);
$request = new ServerRequest(['REQUEST_METHOD' => 'GET'], [], '/foo', 'GET');
- $result = $app->process($request, $delegate->reveal());
+ $result = $app->process($request, $handler->reveal());
$this->assertSame($expected, $result);
}
}
diff --git a/test/TestAsset/CallableInteropMiddleware.php b/test/TestAsset/CallableInteropMiddleware.php
index 74e99801..cf620678 100644
--- a/test/TestAsset/CallableInteropMiddleware.php
+++ b/test/TestAsset/CallableInteropMiddleware.php
@@ -1,20 +1,23 @@
process($request);
+ $response = $handler->handle($request);
+
return $response->withHeader('X-Callable-Interop-Middleware', __CLASS__);
}
diff --git a/test/TestAsset/ContainerException.php b/test/TestAsset/ContainerException.php
index d438ef8e..5d672192 100644
--- a/test/TestAsset/ContainerException.php
+++ b/test/TestAsset/ContainerException.php
@@ -1,10 +1,12 @@