├── .coveralls.yml ├── .docheader ├── src ├── Exception │ ├── RuntimeException.php │ ├── BadMethodCallException.php │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ ├── DuplicateRouteException.php │ ├── InvalidMiddlewareException.php │ ├── MissingDependencyException.php │ └── ContainerNotRegisteredException.php ├── Container │ ├── Exception │ │ ├── ExceptionInterface.php │ │ ├── InvalidArgumentException.php │ │ ├── InvalidServiceException.php │ │ └── NotFoundException.php │ ├── TemplatedErrorHandlerFactory.php │ ├── WhoopsPageHandlerFactory.php │ ├── WhoopsFactory.php │ ├── WhoopsErrorHandlerFactory.php │ └── ApplicationFactory.php ├── AppFactory.php ├── Emitter │ └── EmitterStack.php ├── ErrorMiddlewarePipe.php ├── WhoopsErrorHandler.php ├── MarshalMiddlewareTrait.php ├── TemplatedErrorHandler.php └── Application.php ├── LICENSE.md ├── composer.json ├── CONDUCT.md ├── README.md ├── CONTRIBUTING.md ├── config.xml └── CHANGELOG.md /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: clover.xml 2 | json_path: coveralls-upload.json 3 | -------------------------------------------------------------------------------- /.docheader: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/zendframework/zend-expressive for the canonical source repository 3 | * @copyright Copyright (c) %regexp:(20\d{2}-)20\d{2}% Zend Technologies USA Inc. (http://www.zend.com) 4 | * @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License 5 | */ 6 | -------------------------------------------------------------------------------- /src/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | =1.1.0 < 1.3.0 || >=1.3.1 <2.0.0" 26 | }, 27 | "require-dev": { 28 | "filp/whoops": "^1.1 || ^2.0", 29 | "phpunit/phpunit": "^4.7", 30 | "zendframework/zend-coding-standard": "~1.0.0", 31 | "zendframework/zend-expressive-aurarouter": "^1.0", 32 | "zendframework/zend-expressive-fastroute": "^1.0", 33 | "zendframework/zend-expressive-zendrouter": "^1.0", 34 | "zendframework/zend-servicemanager": "^2.6", 35 | "malukenho/docheader": "^0.1.5" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Zend\\Expressive\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "ZendTest\\Expressive\\": "test/" 45 | } 46 | }, 47 | "suggest": { 48 | "filp/whoops": "^2.0 to use the Whoops error handler", 49 | "zendframework/zend-expressive-helpers": "^1.0 for its UrlHelper, ServerUrlHelper, and BodyParseMiddleware", 50 | "aura/di": "3.0.*@beta to make use of Aura.Di dependency injection container", 51 | "xtreamwayz/pimple-container-interop": "^1.0 to use Pimple for dependency injection", 52 | "zendframework/zend-servicemanager": "^2.5 to use zend-servicemanager for dependency injection" 53 | }, 54 | "scripts": { 55 | "check": [ 56 | "@cs-check", 57 | "@test" 58 | ], 59 | "upload-coverage": "coveralls -v", 60 | "cs-check": "phpcs", 61 | "cs-fix": "phpcbf", 62 | "test": "phpunit", 63 | "test-coverage": "phpunit --coverage-clover clover.xml", 64 | "license-check": "vendor/bin/docheader check src/" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Container/TemplatedErrorHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 32 | * 'zend-expressive' => [ 33 | * 'error_handler' => [ 34 | * 'template_404' => 'name of 404 template', 35 | * 'template_error' => 'name of error template', 36 | * ], 37 | * ] 38 | * 39 | * 40 | * If any of the keys are missing, default values will be used. 41 | */ 42 | class TemplatedErrorHandlerFactory 43 | { 44 | public function __invoke(ContainerInterface $container) 45 | { 46 | $template = $container->has(TemplateRendererInterface::class) 47 | ? $container->get(TemplateRendererInterface::class) 48 | : null; 49 | 50 | $config = $container->has('config') 51 | ? $container->get('config') 52 | : []; 53 | 54 | $config = isset($config['zend-expressive']['error_handler']) 55 | ? $config['zend-expressive']['error_handler'] 56 | : []; 57 | 58 | return new TemplatedErrorHandler( 59 | $template, 60 | (isset($config['template_404']) ? $config['template_404'] : 'error/404'), 61 | (isset($config['template_error']) ? $config['template_error'] : 'error/error') 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | The Zend Framework project adheres to [The Code Manifesto](http://codemanifesto.com) 4 | as its guidelines for contributor interactions. 5 | 6 | ## The Code Manifesto 7 | 8 | We want to work in an ecosystem that empowers developers to reach their 9 | potential — one that encourages growth and effective collaboration. A space that 10 | is safe for all. 11 | 12 | A space such as this benefits everyone that participates in it. It encourages 13 | new developers to enter our field. It is through discussion and collaboration 14 | that we grow, and through growth that we improve. 15 | 16 | In the effort to create such a place, we hold to these values: 17 | 18 | 1. **Discrimination limits us.** This includes discrimination on the basis of 19 | race, gender, sexual orientation, gender identity, age, nationality, technology 20 | and any other arbitrary exclusion of a group of people. 21 | 2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort 22 | levels. Remember that, and if brought to your attention, heed it. 23 | 3. **We are our biggest assets.** None of us were born masters of our trade. 24 | Each of us has been helped along the way. Return that favor, when and where 25 | you can. 26 | 4. **We are resources for the future.** As an extension of #3, share what you 27 | know. Make yourself a resource to help those that come after you. 28 | 5. **Respect defines us.** Treat others as you wish to be treated. Make your 29 | discussions, criticisms and debates from a position of respectfulness. Ask 30 | yourself, is it true? Is it necessary? Is it constructive? Anything less is 31 | unacceptable. 32 | 6. **Reactions require grace.** Angry responses are valid, but abusive language 33 | and vindictive actions are toxic. When something happens that offends you, 34 | handle it assertively, but be respectful. Escalate reasonably, and try to 35 | allow the offender an opportunity to explain themselves, and possibly correct 36 | the issue. 37 | 7. **Opinions are just that: opinions.** Each and every one of us, due to our 38 | background and upbringing, have varying opinions. The fact of the matter, is 39 | that is perfectly acceptable. Remember this: if you respect your own 40 | opinions, you should respect the opinions of others. 41 | 8. **To err is human.** You might not intend it, but mistakes do happen and 42 | contribute to build experience. Tolerate honest mistakes, and don't hesitate 43 | to apologize if you make one yourself. 44 | -------------------------------------------------------------------------------- /src/Container/WhoopsPageHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 24 | * 'whoops' => [ 25 | * 'editor' => 'editor name, editor service name, or callable', 26 | * ] 27 | * 28 | * 29 | * If an editor is provided, it checks to see if it maps to a known service in 30 | * the container, and will use that; otherwise, it uses the value verbatim. 31 | */ 32 | class WhoopsPageHandlerFactory 33 | { 34 | /** 35 | * @param ContainerInterface $container 36 | * @returns PrettyPageHandler 37 | */ 38 | public function __invoke(ContainerInterface $container) 39 | { 40 | $config = $container->has('config') ? $container->get('config') : []; 41 | $config = isset($config['whoops']) ? $config['whoops'] : []; 42 | 43 | $pageHandler = new PrettyPageHandler(); 44 | 45 | $this->injectEditor($pageHandler, $config, $container); 46 | 47 | return $pageHandler; 48 | } 49 | 50 | /** 51 | * Inject an editor into the whoops configuration. 52 | * 53 | * @see https://github.com/filp/whoops/blob/master/docs/Open%20Files%20In%20An%20Editor.md 54 | * @param PrettyPageHandler $handler 55 | * @param array|\ArrayAccess $config 56 | * @param ContainerInterface $container 57 | * @throws Exception\InvalidServiceException for an invalid editor definition. 58 | */ 59 | private function injectEditor(PrettyPageHandler $handler, $config, ContainerInterface $container) 60 | { 61 | if (! isset($config['editor'])) { 62 | return; 63 | } 64 | 65 | $editor = $config['editor']; 66 | 67 | if (is_callable($editor)) { 68 | $handler->setEditor($editor); 69 | return; 70 | } 71 | 72 | if (! is_string($editor)) { 73 | throw new Exception\InvalidServiceException(sprintf( 74 | 'Whoops editor must be a string editor name, string service name, or callable; received "%s"', 75 | (is_object($editor) ? get_class($editor) : gettype($editor)) 76 | )); 77 | } 78 | 79 | if ($container->has($editor)) { 80 | $editor = $container->get($editor); 81 | } 82 | 83 | $handler->setEditor($editor); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Container/WhoopsFactory.php: -------------------------------------------------------------------------------- 1 | 29 | * 'whoops' => [ 30 | * 'json_exceptions' => [ 31 | * 'display' => true, 32 | * 'show_trace' => true, 33 | * 'ajax_only' => true, 34 | * ] 35 | * ] 36 | * 37 | * 38 | * All values are booleans; omission of any implies boolean false. 39 | */ 40 | class WhoopsFactory 41 | { 42 | /** 43 | * Create and return an instance of the Whoops runner. 44 | * 45 | * @param ContainerInterface $container 46 | * @return Whoops 47 | */ 48 | public function __invoke(ContainerInterface $container) 49 | { 50 | $config = $container->has('config') ? $container->get('config') : []; 51 | $config = isset($config['whoops']) ? $config['whoops'] : []; 52 | 53 | $whoops = new Whoops(); 54 | $whoops->writeToOutput(false); 55 | $whoops->allowQuit(false); 56 | $whoops->pushHandler($container->get('Zend\Expressive\WhoopsPageHandler')); 57 | $this->registerJsonHandler($whoops, $config); 58 | $whoops->register(); 59 | return $whoops; 60 | } 61 | 62 | /** 63 | * If configuration indicates a JsonResponseHandler, configure and register it. 64 | * 65 | * @param Whoops $whoops 66 | * @param array|\ArrayAccess $config 67 | */ 68 | private function registerJsonHandler(Whoops $whoops, $config) 69 | { 70 | if (! isset($config['json_exceptions']['display']) 71 | || empty($config['json_exceptions']['display']) 72 | ) { 73 | return; 74 | } 75 | 76 | $handler = new JsonResponseHandler(); 77 | 78 | if (isset($config['json_exceptions']['show_trace'])) { 79 | $handler->addTraceToOutput(true); 80 | } 81 | 82 | if (isset($config['json_exceptions']['ajax_only'])) { 83 | if (method_exists(\Whoops\Util\Misc::class, 'isAjaxRequest')) { 84 | // Whoops 2.x 85 | if (! \Whoops\Util\Misc::isAjaxRequest()) { 86 | return; 87 | } 88 | } elseif (method_exists($handler, 'onlyForAjaxRequests')) { 89 | // Whoops 1.x 90 | $handler->onlyForAjaxRequests(true); 91 | } 92 | } 93 | 94 | $whoops->pushHandler($handler); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/AppFactory.php: -------------------------------------------------------------------------------- 1 | push(new SapiEmitter()); 72 | 73 | return new Application($router, $container, null, $emitter); 74 | } 75 | 76 | /** 77 | * Do not allow instantiation. 78 | * @codeCoverageIgnore 79 | */ 80 | private function __construct() 81 | { 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Emitter/EmitterStack.php: -------------------------------------------------------------------------------- 1 | emit($response)) { 44 | $completed = true; 45 | break; 46 | } 47 | } 48 | 49 | return ($completed ? null : false); 50 | } 51 | 52 | /** 53 | * Set an emitter on the stack by index. 54 | * 55 | * @param mixed $index 56 | * @param EmitterInterface $emitter 57 | * @throws InvalidArgumentException if not an EmitterInterface instance 58 | */ 59 | public function offsetSet($index, $emitter) 60 | { 61 | $this->validateEmitter($emitter); 62 | parent::offsetSet($index, $emitter); 63 | } 64 | 65 | /** 66 | * Push an emitter to the stack. 67 | * 68 | * @param EmitterInterface $emitter 69 | * @throws InvalidArgumentException if not an EmitterInterface instance 70 | */ 71 | public function push($emitter) 72 | { 73 | $this->validateEmitter($emitter); 74 | parent::push($emitter); 75 | } 76 | 77 | /** 78 | * Unshift an emitter to the stack. 79 | * 80 | * @param EmitterInterface $emitter 81 | * @throws InvalidArgumentException if not an EmitterInterface instance 82 | */ 83 | public function unshift($emitter) 84 | { 85 | $this->validateEmitter($emitter); 86 | parent::unshift($emitter); 87 | } 88 | 89 | /** 90 | * Validate that an emitter implements EmitterInterface. 91 | * 92 | * @param mixed $emitter 93 | * @throws InvalidArgumentException for non-emitter instances 94 | */ 95 | private function validateEmitter($emitter) 96 | { 97 | if (! $emitter instanceof EmitterInterface) { 98 | throw new Exception\InvalidArgumentException(sprintf( 99 | '%s expects an EmitterInterface implementation', 100 | __CLASS__ 101 | )); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-expressive 2 | 3 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-expressive.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-expressive) 4 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-expressive/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-expressive?branch=master) 5 | 6 | *Develop PSR-7 middleware applications in minutes!* 7 | 8 | zend-expressive builds on [zend-stratigility](https://github.com/zendframework/zend-stratigility) 9 | to provide a minimalist PSR-7 middleware framework for PHP, with the following 10 | features: 11 | 12 | - Routing. Choose your own router; we support: 13 | - [Aura.Router](https://github.com/auraphp/Aura.Router) 14 | - [FastRoute](https://github.com/nikic/FastRoute) 15 | - [zend-router](https://github.com/zendframework/zend-expressive-router) 16 | - DI Containers, via [container-interop](https://github.com/container-interop/container-interop). 17 | Middleware matched via routing is retrieved from the composed container. 18 | - Optionally, templating. We support: 19 | - [Plates](http://platesphp.com/) 20 | - [Twig](http://twig.sensiolabs.org/) 21 | - [ZF2's PhpRenderer](https://github.com/zendframework/zend-view) 22 | 23 | ## Installation 24 | 25 | We provide two ways to install Expressive, both using 26 | [Composer](https://getcomposer.org): via our 27 | [skeleton project and installer](https://github.com/zendframework/zend-expressive-skeleton), 28 | or manually. 29 | 30 | ### Using the skeleton + installer 31 | 32 | The simplest way to install and get started is using the skeleton project, which 33 | includes installer scripts for choosing a router, dependency injection 34 | container, and optionally a template renderer and/or error handler. The skeleton 35 | also provides configuration for officially supported dependencies. 36 | 37 | To use the skeleton, use Composer's `create-project` command: 38 | 39 | ```bash 40 | $ composer create-project zendframework/zend-expressive-skeleton 41 | ``` 42 | 43 | This will prompt you through choosing your dependencies, and then create and 44 | install the project in the `` (omitting the `` will 45 | create and install in a `zend-expressive-skeleton/` directory). 46 | 47 | ### Manual Composer installation 48 | 49 | You can install Expressive standalone using Composer: 50 | 51 | ```bash 52 | $ composer require zendframework/zend-expressive 53 | ``` 54 | 55 | However, at this point, Expressive is not usable, as you need to supply 56 | minimally: 57 | 58 | - a router. 59 | - a dependency injection container. 60 | 61 | We currently support and provide the following routing integrations: 62 | 63 | - [Aura.Router](https://github.com/auraphp/Aura.Router): 64 | `composer require zendframework/zend-expressive-aurarouter` 65 | - [FastRoute](https://github.com/nikic/FastRoute): 66 | `composer require zendframework/zend-expressive-fastroute` 67 | - [zend-router](https://github.com/zendframework/zend-expressive-router): 68 | `composer require zendframework/zend-expressive-zendrouter` 69 | 70 | We recommend using a dependency injection container, and typehint against 71 | [container-interop](https://github.com/container-interop/container-interop). We 72 | can recommend the following implementations: 73 | 74 | - [zend-servicemanager](https://github.com/zendframework/zend-servicemanager): 75 | `composer require zendframework/zend-servicemanager` 76 | - [pimple-container-interop](https://github.com/xtreamwayz/pimple-container-interop): 77 | `composer require xtreamwayz/pimple-container-interop` 78 | - [Aura.Di](https://github.com/auraphp/Aura.Di): 79 | `composer require aura/di:3.0.*@beta` 80 | 81 | Additionally, you may optionally want to install a template renderer 82 | implementation, and/or an error handling integration. These are covered in the 83 | documentation. 84 | 85 | ## Documentation 86 | 87 | Documentation is [in the doc tree](doc/book/), and can be compiled using [mkdocs](http://www.mkdocs.org): 88 | 89 | ```bash 90 | $ mkdocs build 91 | ``` 92 | 93 | Additionally, public-facing, browseable documentation is available at 94 | https://zendframework.github.io/zend-expressive/ 95 | 96 | ## Architecture 97 | 98 | Architectural notes are in [NOTES.md](NOTES.md). 99 | 100 | Please see the tests for full information on capabilities. 101 | -------------------------------------------------------------------------------- /src/ErrorMiddlewarePipe.php: -------------------------------------------------------------------------------- 1 | pipeline = $pipeline; 45 | } 46 | 47 | /** 48 | * Handle an error request. 49 | * 50 | * This is essentially a version of the MiddlewarePipe that acts as a pipeline 51 | * for solely error middleware; it's primary use case is to allow configuring 52 | * arrays of error middleware as a single pipeline. 53 | * 54 | * Operation is identical to MiddlewarePipe, with the single exception that 55 | * $next is called with the $error argument. 56 | * 57 | * @param mixed $error 58 | * @param Request $request 59 | * @param Response $response 60 | * @param callable $out 61 | * @return Response 62 | */ 63 | public function __invoke($error, Request $request, Response $response, callable $out) 64 | { 65 | // Decorate instances with Stratigility decorators; required to work 66 | // with Next implementation. 67 | $request = $this->decorateRequest($request); 68 | $response = $this->decorateResponse($response); 69 | 70 | $pipeline = $this->getInternalPipeline(); 71 | $done = $out ?: new FinalHandler([], $response); 72 | $next = new Next($pipeline, $done); 73 | $result = $next($request, $response, $error); 74 | 75 | return ($result instanceof Response ? $result : $response); 76 | } 77 | 78 | /** 79 | * Retrieve the internal pipeline from the composed MiddlewarePipe. 80 | * 81 | * Uses reflection to retrieve the internal pipeline from the composed 82 | * MiddlewarePipe, in order to allow using it to create a Next instance. 83 | * 84 | * @return \SplQueue 85 | */ 86 | private function getInternalPipeline() 87 | { 88 | $r = new ReflectionProperty($this->pipeline, 'pipeline'); 89 | $r->setAccessible(true); 90 | return $r->getValue($this->pipeline); 91 | } 92 | 93 | /** 94 | * Decorate the request with the Stratigility decorator. 95 | * 96 | * Proxies to the composed MiddlewarePipe's equivalent method. 97 | * 98 | * @param Request $request 99 | * @return \Zend\Stratigility\Http\Request 100 | */ 101 | private function decorateRequest(Request $request) 102 | { 103 | $r = new ReflectionMethod($this->pipeline, 'decorateRequest'); 104 | $r->setAccessible(true); 105 | return $r->invoke($this->pipeline, $request); 106 | } 107 | 108 | /** 109 | * Decorate the response with the Stratigility decorator. 110 | * 111 | * Proxies to the composed MiddlewarePipe's equivalent method. 112 | * 113 | * @param Response $response 114 | * @return \Zend\Stratigility\Http\Response 115 | */ 116 | private function decorateResponse(Response $response) 117 | { 118 | $r = new ReflectionMethod($this->pipeline, 'decorateResponse'); 119 | $r->setAccessible(true); 120 | return $r->invoke($this->pipeline, $response); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/WhoopsErrorHandler.php: -------------------------------------------------------------------------------- 1 | whoops = $whoops; 58 | $this->whoopsHandler = $whoopsHandler; 59 | parent::__construct($renderer, $template404, $templateError, $originalResponse); 60 | } 61 | 62 | /** 63 | * Handle an exception. 64 | * 65 | * Calls on prepareWhoopsHandler() to inject additional data tables into 66 | * the generated payload, and then injects the response with the result 67 | * of whoops handling the exception. 68 | * 69 | * @param \Throwable $exception 70 | * @param Request $request 71 | * @param Response $response 72 | * @return Response 73 | */ 74 | protected function handleException($exception, Request $request, Response $response) 75 | { 76 | // Push the whoops handler if any 77 | if ($this->whoopsHandler !== null) { 78 | $this->whoops->pushHandler($this->whoopsHandler); 79 | } 80 | 81 | // Walk through all handlers 82 | foreach ($this->whoops->getHandlers() as $handler) { 83 | // Add fancy data for the PrettyPageHandler 84 | if ($handler instanceof PrettyPageHandler) { 85 | $this->prepareWhoopsHandler($request, $handler); 86 | } 87 | } 88 | 89 | $response 90 | ->getBody() 91 | ->write($this->whoops->handleException($exception)); 92 | 93 | return $response; 94 | } 95 | 96 | /** 97 | * Prepare the Whoops page handler with a table displaying request information 98 | * 99 | * @param Request $request 100 | * @param PrettyPageHandler $handler 101 | */ 102 | private function prepareWhoopsHandler(Request $request, PrettyPageHandler $handler) 103 | { 104 | $uri = $request->getAttribute('originalUri', false) ?: $request->getUri(); 105 | $request = $request->getAttribute('originalRequest', false) ?: $request; 106 | 107 | $handler->addDataTable('Expressive Application Request', [ 108 | 'HTTP Method' => $request->getMethod(), 109 | 'URI' => (string) $uri, 110 | 'Script' => $request->getServerParams()['SCRIPT_NAME'], 111 | 'Headers' => $request->getHeaders(), 112 | 'Cookies' => $request->getCookieParams(), 113 | 'Attributes' => $request->getAttributes(), 114 | 'Query String Arguments' => $request->getQueryParams(), 115 | 'Body Params' => $request->getParsedBody(), 116 | ]); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Container/WhoopsErrorHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 40 | * 'zend-expressive' => [ 41 | * 'error_handler' => [ 42 | * 'template_404' => 'name of 404 template', 43 | * 'template_error' => 'name of error template', 44 | * ], 45 | * ] 46 | * 47 | * 48 | * If any of the keys are missing, default values will be used. 49 | * 50 | * The whoops configuration can contain: 51 | * 52 | * 53 | * 'whoops' => [ 54 | * 'json_exceptions' => [ 55 | * 'display' => true, 56 | * 'show_trace' => true, 57 | * 'ajax_only' => true, 58 | * ] 59 | * ] 60 | * 61 | * 62 | * All values are booleans; omission of any implies boolean false. 63 | */ 64 | class WhoopsErrorHandlerFactory 65 | { 66 | public function __invoke(ContainerInterface $container) 67 | { 68 | $template = $container->has(TemplateRendererInterface::class) 69 | ? $container->get(TemplateRendererInterface::class) 70 | : null; 71 | 72 | $config = $container->has('config') 73 | ? $container->get('config') 74 | : []; 75 | 76 | $expressiveConfig = isset($config['zend-expressive']['error_handler']) 77 | ? $config['zend-expressive']['error_handler'] 78 | : []; 79 | 80 | $whoopsConfig = isset($config['whoops']) 81 | ? $config['whoops'] 82 | : []; 83 | 84 | $whoops = $container->get('Zend\Expressive\Whoops'); 85 | $whoops->pushHandler($container->get('Zend\Expressive\WhoopsPageHandler')); 86 | $this->registerJsonHandler($whoops, $whoopsConfig); 87 | 88 | return new WhoopsErrorHandler( 89 | $whoops, 90 | null, 91 | $template, 92 | (isset($expressiveConfig['template_404']) ? $expressiveConfig['template_404'] : 'error/404'), 93 | (isset($expressiveConfig['template_error']) ? $expressiveConfig['template_error'] : 'error/error') 94 | ); 95 | } 96 | 97 | /** 98 | * If configuration indicates a JsonResponseHandler, configure and register it. 99 | * 100 | * @param Whoops $whoops 101 | * @param array|\ArrayAccess $config 102 | */ 103 | private function registerJsonHandler(Whoops $whoops, $config) 104 | { 105 | if (! isset($config['json_exceptions']['display']) 106 | || empty($config['json_exceptions']['display']) 107 | ) { 108 | return; 109 | } 110 | 111 | $handler = new JsonResponseHandler(); 112 | 113 | if (isset($config['json_exceptions']['ajax_only'])) { 114 | if (method_exists(\Whoops\Util\Misc::class, 'isAjaxRequest')) { 115 | // Whoops 2.x 116 | if (! \Whoops\Util\Misc::isAjaxRequest()) { 117 | return; 118 | } 119 | } elseif (method_exists($handler, 'onlyForAjaxRequests')) { 120 | // Whoops 1.x 121 | $handler->onlyForAjaxRequests(true); 122 | } 123 | } 124 | 125 | if (isset($config['json_exceptions']['show_trace'])) { 126 | $handler->addTraceToOutput(true); 127 | } 128 | 129 | $whoops->pushHandler($handler); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/MarshalMiddlewareTrait.php: -------------------------------------------------------------------------------- 1 | marshalMiddlewarePipe($middleware, $container, $forError); 46 | } 47 | 48 | if (is_string($middleware) && $container && $container->has($middleware)) { 49 | $method = $forError ? 'marshalLazyErrorMiddlewareService' : 'marshalLazyMiddlewareService'; 50 | return $this->{$method}($middleware, $container); 51 | } 52 | 53 | $callable = $middleware; 54 | if (is_string($middleware)) { 55 | $callable = $this->marshalInvokableMiddleware($middleware); 56 | } 57 | 58 | if (! is_callable($callable)) { 59 | throw new Exception\InvalidMiddlewareException( 60 | sprintf( 61 | 'Unable to resolve middleware "%s" to a callable', 62 | (is_object($middleware) 63 | ? get_class($middleware) . "[Object]" 64 | : gettype($middleware) . '[Scalar]') 65 | ) 66 | ); 67 | } 68 | 69 | return $callable; 70 | } 71 | 72 | /** 73 | * Marshal a middleware pipe from an array of middleware. 74 | * 75 | * Each item in the array can be one of the following: 76 | * 77 | * - A callable middleware 78 | * - A string service name of middleware to retrieve from the container 79 | * - A string class name of a constructor-less middleware class to 80 | * instantiate 81 | * 82 | * As each middleware is verified, it is piped to the middleware pipe. 83 | * 84 | * @param array $middlewares 85 | * @param null|ContainerInterface $container 86 | * @param bool $forError Whether or not the middleware pipe generated is 87 | * intended to be populated with error middleware; defaults to false. 88 | * @return MiddlewarePipe|ErrorMiddlewarePipe When $forError is true, 89 | * returns an ErrorMiddlewarePipe. 90 | * @throws Exception\InvalidMiddlewareException for any invalid middleware items. 91 | */ 92 | private function marshalMiddlewarePipe(array $middlewares, ContainerInterface $container = null, $forError = false) 93 | { 94 | $middlewarePipe = new MiddlewarePipe(); 95 | 96 | foreach ($middlewares as $middleware) { 97 | $middlewarePipe->pipe( 98 | $this->prepareMiddleware($middleware, $container, $forError) 99 | ); 100 | } 101 | 102 | if ($forError) { 103 | return new ErrorMiddlewarePipe($middlewarePipe); 104 | } 105 | 106 | return $middlewarePipe; 107 | } 108 | 109 | /** 110 | * Attempt to instantiate the given middleware. 111 | * 112 | * @param string $middleware 113 | * @return string|callable Returns $middleware intact on failure, and the 114 | * middleware instance on success. 115 | */ 116 | private function marshalInvokableMiddleware($middleware) 117 | { 118 | if (! class_exists($middleware)) { 119 | return $middleware; 120 | } 121 | 122 | return new $middleware(); 123 | } 124 | 125 | /** 126 | * @param string $middleware 127 | * @param ContainerInterface $container 128 | * @return callable 129 | */ 130 | private function marshalLazyMiddlewareService($middleware, ContainerInterface $container) 131 | { 132 | return function ($request, $response, $next = null) use ($container, $middleware) { 133 | $invokable = $container->get($middleware); 134 | if (! is_callable($invokable)) { 135 | throw new Exception\InvalidMiddlewareException(sprintf( 136 | 'Lazy-loaded middleware "%s" is not invokable', 137 | $middleware 138 | )); 139 | } 140 | return $invokable($request, $response, $next); 141 | }; 142 | } 143 | 144 | /** 145 | * @param string $middleware 146 | * @param ContainerInterface $container 147 | * @return callable 148 | */ 149 | private function marshalLazyErrorMiddlewareService($middleware, ContainerInterface $container) 150 | { 151 | return function ($error, $request, $response, $next) use ($container, $middleware) { 152 | $invokable = $container->get($middleware); 153 | if (! is_callable($invokable)) { 154 | throw new Exception\InvalidMiddlewareException(sprintf( 155 | 'Lazy-loaded middleware "%s" is not invokable', 156 | $middleware 157 | )); 158 | } 159 | return $invokable($error, $request, $response, $next); 160 | }; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | ## RESOURCES 4 | 5 | If you wish to contribute to Zend Framework, please be sure to 6 | read/subscribe to the following resources: 7 | 8 | - [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards) 9 | - [Contributor's Guide](CONTRIBUTING.md) 10 | - ZF Contributor's mailing list: 11 | Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html 12 | Subscribe: zf-contributors-subscribe@lists.zend.com 13 | - ZF Contributor's IRC channel: 14 | #zftalk.dev on Freenode.net 15 | 16 | If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-expressive/issues/new). 17 | 18 | ## Reporting Potential Security Issues 19 | 20 | If you have encountered a potential security vulnerability, please **DO NOT** report it on the public 21 | issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead. 22 | We will work with you to verify the vulnerability and patch it as soon as possible. 23 | 24 | When reporting issues, please provide the following information: 25 | 26 | - Component(s) affected 27 | - A description indicating how to reproduce the issue 28 | - A summary of the security vulnerability and impact 29 | 30 | We request that you contact us via the email address above and give the project 31 | contributors a chance to resolve the vulnerability and issue a new release prior 32 | to any public exposure; this helps protect users and provides them with a chance 33 | to upgrade and/or update in order to protect their applications. 34 | 35 | For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc). 36 | 37 | ## RUNNING TESTS 38 | 39 | To run tests: 40 | 41 | - Clone the repository: 42 | 43 | ```console 44 | $ git clone git@github.com:zendframework/zend-expressive.git 45 | $ cd zend-expressive 46 | ``` 47 | 48 | - Install dependencies via composer: 49 | 50 | ```console 51 | $ curl -sS https://getcomposer.org/installer | php -- 52 | $ ./composer.phar install 53 | ``` 54 | 55 | If you don't have `curl` installed, you can also download `composer.phar` from https://getcomposer.org/ 56 | 57 | - Run the tests using the "test" command shipped in the `composer.json`: 58 | 59 | ```console 60 | $ composer test 61 | ``` 62 | 63 | You can turn on conditional tests with the `phpunit.xml` file. 64 | To do so: 65 | 66 | - Copy `phpunit.xml.dist` file to `phpunit.xml` 67 | - Edit `phpunit.xml` to enable any specific functionality you 68 | want to test, as well as to provide test values to utilize. 69 | 70 | ## Running Coding Standards Checks 71 | 72 | First, ensure you've installed dependencies via composer, per the previous 73 | section on running tests. 74 | 75 | To run CS checks only: 76 | 77 | ```console 78 | $ composer cs-check 79 | ``` 80 | 81 | To attempt to automatically fix common CS issues: 82 | 83 | 84 | ```console 85 | $ composer cs-fix 86 | ``` 87 | 88 | If the above fixes any CS issues, please re-run the tests to ensure 89 | they pass, and make sure you add and commit the changes after verification. 90 | 91 | ## Recommended Workflow for Contributions 92 | 93 | Your first step is to establish a public repository from which we can 94 | pull your work into the master repository. We recommend using 95 | [GitHub](https://github.com), as that is where the component is already hosted. 96 | 97 | 1. Setup a [GitHub account](http://github.com/), if you haven't yet 98 | 2. Fork the repository (http://github.com/zendframework/zend-expressive) 99 | 3. Clone the canonical repository locally and enter it. 100 | 101 | ```console 102 | $ git clone git://github.com:zendframework/zend-expressive.git 103 | $ cd zend-expressive 104 | ``` 105 | 106 | 4. Add a remote to your fork; substitute your GitHub username in the command 107 | below. 108 | 109 | ```console 110 | $ git remote add {username} git@github.com:{username}/zend-expressive.git 111 | $ git fetch {username} 112 | ``` 113 | 114 | ### Keeping Up-to-Date 115 | 116 | Periodically, you should update your fork or personal repository to 117 | match the canonical ZF repository. Assuming you have setup your local repository 118 | per the instructions above, you can do the following: 119 | 120 | 121 | ```console 122 | $ git checkout master 123 | $ git fetch origin 124 | $ git rebase origin/master 125 | # OPTIONALLY, to keep your remote up-to-date - 126 | $ git push {username} master:master 127 | ``` 128 | 129 | If you're tracking other branches -- for example, the "develop" branch, where 130 | new feature development occurs -- you'll want to do the same operations for that 131 | branch; simply substitute "develop" for "master". 132 | 133 | ### Working on a patch 134 | 135 | We recommend you do each new feature or bugfix in a new branch. This simplifies 136 | the task of code review as well as the task of merging your changes into the 137 | canonical repository. 138 | 139 | A typical workflow will then consist of the following: 140 | 141 | 1. Create a new local branch based off either your master or develop branch. 142 | 2. Switch to your new local branch. (This step can be combined with the 143 | previous step with the use of `git checkout -b`.) 144 | 3. Do some work, commit, repeat as necessary. 145 | 4. Push the local branch to your remote repository. 146 | 5. Send a pull request. 147 | 148 | The mechanics of this process are actually quite trivial. Below, we will 149 | create a branch for fixing an issue in the tracker. 150 | 151 | ```console 152 | $ git checkout -b hotfix/9295 153 | Switched to a new branch 'hotfix/9295' 154 | ``` 155 | 156 | ... do some work ... 157 | 158 | 159 | ```console 160 | $ git commit 161 | ``` 162 | 163 | ... write your log message ... 164 | 165 | 166 | ```console 167 | $ git push {username} hotfix/9295:hotfix/9295 168 | Counting objects: 38, done. 169 | Delta compression using up to 2 threads. 170 | Compression objects: 100% (18/18), done. 171 | Writing objects: 100% (20/20), 8.19KiB, done. 172 | Total 20 (delta 12), reused 0 (delta 0) 173 | To ssh://git@github.com/{username}/zend-expressive.git 174 | b5583aa..4f51698 HEAD -> master 175 | ``` 176 | 177 | To send a pull request, you have two options. 178 | 179 | If using GitHub, you can do the pull request from there. Navigate to 180 | your repository, select the branch you just created, and then select the 181 | "Pull Request" button in the upper right. Select the user/organization 182 | "zendframework" as the recipient. 183 | 184 | If using your own repository - or even if using GitHub - you can use `git 185 | format-patch` to create a patchset for us to apply; in fact, this is 186 | **recommended** for security-related patches. If you use `format-patch`, please 187 | send the patches as attachments to: 188 | 189 | - zf-devteam@zend.com for patches without security implications 190 | - zf-security@zend.com for security patches 191 | 192 | #### What branch to issue the pull request against? 193 | 194 | Which branch should you issue a pull request against? 195 | 196 | - For fixes against the stable release, issue the pull request against the 197 | "master" branch. 198 | - For new features, or fixes that introduce new elements to the public API (such 199 | as new public methods or properties), issue the pull request against the 200 | "develop" branch. 201 | 202 | ### Branch Cleanup 203 | 204 | As you might imagine, if you are a frequent contributor, you'll start to 205 | get a ton of branches both locally and on your remote. 206 | 207 | Once you know that your changes have been accepted to the master 208 | repository, we suggest doing some cleanup of these branches. 209 | 210 | - Local branch cleanup 211 | 212 | ```console 213 | $ git branch -d 214 | ``` 215 | 216 | - Remote branch removal 217 | 218 | ```console 219 | $ git push {username} : 220 | ``` 221 | 222 | 223 | ## Conduct 224 | 225 | Please see our [CONDUCT.md](CONDUCT.md) to understand expected behavior when interacting with others in the project. 226 | -------------------------------------------------------------------------------- /src/TemplatedErrorHandler.php: -------------------------------------------------------------------------------- 1 | renderer = $renderer; 80 | $this->template404 = $template404; 81 | $this->templateError = $templateError; 82 | if ($originalResponse) { 83 | $this->setOriginalResponse($originalResponse); 84 | } 85 | } 86 | 87 | /** 88 | * Set the original response for comparisons. 89 | * 90 | * @param Response $response 91 | */ 92 | public function setOriginalResponse(Response $response) 93 | { 94 | $this->bodySize = $response->getBody()->getSize(); 95 | $this->originalResponse = $response; 96 | } 97 | 98 | /** 99 | * Final handler for an application. 100 | * 101 | * @param Request $request 102 | * @param Response $response 103 | * @param null|mixed $err 104 | * @return Response 105 | */ 106 | public function __invoke(Request $request, Response $response, $err = null) 107 | { 108 | if (! $err) { 109 | return $this->handlePotentialSuccess($request, $response); 110 | } 111 | 112 | return $this->handleErrorResponse($err, $request, $response); 113 | } 114 | 115 | /** 116 | * Handle a non-exception error. 117 | * 118 | * If a template renderer is present, passes the following to the template 119 | * specified in the $templateError property: 120 | * 121 | * - error (the error itself) 122 | * - uri 123 | * - status (response status) 124 | * - reason (reason associated with response status) 125 | * - request (full PSR-7 request instance) 126 | * - response (full PSR-7 response instance) 127 | * 128 | * The results of rendering are then written to the response body. 129 | * 130 | * This method may be used as an extension point. 131 | * 132 | * @param mixed $error 133 | * @param Request $request 134 | * @param Response $response 135 | * @return Response 136 | */ 137 | protected function handleError($error, Request $request, Response $response) 138 | { 139 | if ($this->renderer) { 140 | $stream = new Stream('php://temp', 'wb+'); 141 | $stream->write( 142 | $this->renderer->render($this->templateError, [ 143 | 'uri' => $request->getUri(), 144 | 'error' => $error, 145 | 'status' => $response->getStatusCode(), 146 | 'reason' => $response->getReasonPhrase(), 147 | 'request' => $request, 148 | 'response' => $response, 149 | ]) 150 | ); 151 | return $response->withBody($stream); 152 | } 153 | 154 | return $response; 155 | } 156 | 157 | /** 158 | * Prepare the exception for display. 159 | * 160 | * Proxies to `handleError()`; exists primarily to as an extension point 161 | * for other handlers. 162 | * 163 | * @param \Throwable $exception 164 | * @param Request $request 165 | * @param Response $response 166 | * @return Response 167 | */ 168 | protected function handleException($exception, Request $request, Response $response) 169 | { 170 | return $this->handleError($exception, $request, $response); 171 | } 172 | 173 | /** 174 | * Handle a non-error condition. 175 | * 176 | * Non-error conditions mean either all middleware called $next(), and we 177 | * have a complete response, or no middleware was able to handle the 178 | * request. 179 | * 180 | * This method determines which occurred, returning the response in the 181 | * first instance, and returning a 404 response in the second. 182 | * 183 | * @param Request $request 184 | * @param Response $response 185 | * @return Response 186 | */ 187 | private function handlePotentialSuccess(Request $request, Response $response) 188 | { 189 | if (! $this->originalResponse) { 190 | // No original response detected; decide whether we have a 191 | // response to return 192 | return $this->marshalReceivedResponse($request, $response); 193 | } 194 | 195 | $originalResponse = $this->originalResponse; 196 | $decoratedResponse = $request->getAttribute('originalResponse', $response); 197 | 198 | if ($originalResponse !== $response 199 | && $originalResponse !== $decoratedResponse 200 | ) { 201 | // Response does not match either the original response or the 202 | // decorated response; return it verbatim. 203 | return $response; 204 | } 205 | 206 | if (($originalResponse === $response || $decoratedResponse === $response) 207 | && $this->bodySize !== $response->getBody()->getSize() 208 | ) { 209 | // Response matches either the original response or the 210 | // decorated response; but the body size has changed; return it 211 | // verbatim. 212 | return $response; 213 | } 214 | 215 | return $this->create404($request, $response); 216 | } 217 | 218 | /** 219 | * Determine whether to return the given response, or a 404. 220 | * 221 | * If no original response was present, we check to see if we have a 200 222 | * response with empty content; if so, we treat it as a 404. 223 | * 224 | * Otherwise, we return the response intact. 225 | * 226 | * @param Request $request 227 | * @param Response $response 228 | * @return Response 229 | */ 230 | private function marshalReceivedResponse(Request $request, Response $response) 231 | { 232 | if ($response->getStatusCode() === 200 233 | && $response->getBody()->getSize() === 0 234 | ) { 235 | return $this->create404($request, $response); 236 | } 237 | 238 | return $response; 239 | } 240 | 241 | /** 242 | * Create a 404 response. 243 | * 244 | * If we have a template renderer composed, renders the 404 template into 245 | * the response. 246 | * 247 | * @param Request $request 248 | * @param Response $response 249 | * @return Response 250 | */ 251 | private function create404(Request $request, Response $response) 252 | { 253 | if ($this->renderer) { 254 | $stream = new Stream('php://temp', 'wb+'); 255 | $stream->write( 256 | $this->renderer->render($this->template404, [ 'uri' => $request->getUri() ]) 257 | ); 258 | $response = $response->withBody($stream); 259 | } 260 | return $response->withStatus(404); 261 | } 262 | 263 | /** 264 | * Handle an error response. 265 | * 266 | * Marshals the response status from the error. 267 | * 268 | * If the error is not an exception, it then proxies to handleError(); 269 | * otherwise, it proxies to handleException(). 270 | * 271 | * @param mixed $error 272 | * @param Request $request 273 | * @param Response $response 274 | * @return Response 275 | */ 276 | private function handleErrorResponse($error, Request $request, Response $response) 277 | { 278 | $response = $response->withStatus(Utils::getStatusCode($error, $response)); 279 | 280 | if (! $error instanceof \Exception && ! $error instanceof \Throwable) { 281 | return $this->handleError($error, $request, $response); 282 | } 283 | 284 | return $this->handleException($error, $request, $response); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/Container/ApplicationFactory.php: -------------------------------------------------------------------------------- 1 | 39 | * return [ 40 | * 'routes' => [ 41 | * [ 42 | * 'path' => '/path/to/match', 43 | * 'middleware' => 'Middleware Service Name or Callable', 44 | * 'allowed_methods' => [ 'GET', 'POST', 'PATCH' ], 45 | * 'options' => [ 46 | * 'stuff' => 'to', 47 | * 'pass' => 'to', 48 | * 'the' => 'underlying router', 49 | * ], 50 | * ], 51 | * // etc. 52 | * ], 53 | * ]; 54 | * 55 | * 56 | * Each route MUST have a path and middleware key at the minimum. 57 | * 58 | * The "allowed_methods" key may be omitted, can be either an array or the 59 | * value of the Zend\Expressive\Router\Route::HTTP_METHOD_ANY constant; any 60 | * valid HTTP method token is allowed, which means you can specify custom HTTP 61 | * methods as well. 62 | * 63 | * The "options" key may also be omitted, and its interpretation will be 64 | * dependent on the underlying router used. 65 | * 66 | * Furthermore, you can define middleware to pipe to the application to run on 67 | * every invocation (assuming they match and/or other middleware does not 68 | * return a response earlier). Use the following configuration: 69 | * 70 | * 71 | * return [ 72 | * 'middleware_pipeline' => [ 73 | * // An array of middleware to register with the pipeline. 74 | * // entries to register prior to routing/dispatching... 75 | * Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE, 76 | * Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE, 77 | * // entries to register after routing/dispatching... 78 | * ], 79 | * ]; 80 | * 81 | * 82 | * Each item in the middleware_pipeline array (with the exception of the routing 83 | * and dispatch middleware entries) must be of the following specification: 84 | * 85 | * 86 | * [ 87 | * // required: 88 | * 'middleware' => 'Name of middleware service, or a callable', 89 | * // optional: 90 | * 'path' => '/path/to/match', 91 | * 'error' => true, 92 | * 'priority' => 1, // integer 93 | * ] 94 | * 95 | * 96 | * Note that the `path` element can only be a literal. `error` indicates 97 | * whether or not the middleware represents error middleware; this is done 98 | * so that Expressive can lazy-load an error middleware service (more below). 99 | * Omitting `error` or setting it to a non-true value is the default, 100 | * indicating the middleware is standard middleware. 101 | * 102 | * `priority` is used to shape the order in which middleware is piped to the 103 | * application. Values are integers, with high values having higher priority 104 | * (piped earlier), and low/negative values having lower priority (piped last). 105 | * Default priority if none is specified is 1. Middleware with the same 106 | * priority are piped in the order in which they appear. 107 | * 108 | * Middleware piped may be either callables or service names. If you specify 109 | * the middleware's `error` flag as `true`, the middleware will be piped using 110 | * `Application::pipeErrorHandler()` instead of `Application::pipe()`. 111 | * 112 | * Additionally, you can specify an array of callables or service names as 113 | * the `middleware` value of a specification. Internally, this will create 114 | * a `Zend\Stratigility\MiddlewarePipe` instance, with the middleware 115 | * specified piped in the order provided. 116 | */ 117 | class ApplicationFactory 118 | { 119 | const DISPATCH_MIDDLEWARE = 'EXPRESSIVE_DISPATCH_MIDDLEWARE'; 120 | const ROUTING_MIDDLEWARE = 'EXPRESSIVE_ROUTING_MIDDLEWARE'; 121 | 122 | /** 123 | * @deprecated This constant will be removed in v1.1. 124 | */ 125 | const ROUTE_RESULT_OBSERVER_MIDDLEWARE = 'EXPRESSIVE_ROUTE_RESULT_OBSERVER_MIDDLEWARE'; 126 | 127 | /** 128 | * Create and return an Application instance. 129 | * 130 | * See the class level docblock for information on what services this 131 | * factory will optionally consume. 132 | * 133 | * @param ContainerInterface $container 134 | * @return Application 135 | */ 136 | public function __invoke(ContainerInterface $container) 137 | { 138 | $router = $container->has(RouterInterface::class) 139 | ? $container->get(RouterInterface::class) 140 | : new FastRouteRouter(); 141 | 142 | $finalHandler = $container->has('Zend\Expressive\FinalHandler') 143 | ? $container->get('Zend\Expressive\FinalHandler') 144 | : null; 145 | 146 | $emitter = $container->has(EmitterInterface::class) 147 | ? $container->get(EmitterInterface::class) 148 | : null; 149 | 150 | $app = new Application($router, $container, $finalHandler, $emitter); 151 | 152 | $this->injectRoutesAndPipeline($app, $container); 153 | 154 | return $app; 155 | } 156 | 157 | /** 158 | * Injects routes and the middleware pipeline into the application. 159 | * 160 | * @param Application $app 161 | * @param ContainerInterface $container 162 | */ 163 | private function injectRoutesAndPipeline(Application $app, ContainerInterface $container) 164 | { 165 | $config = $container->has('config') ? $container->get('config') : []; 166 | $pipelineCreated = false; 167 | 168 | if (isset($config['middleware_pipeline']) && is_array($config['middleware_pipeline'])) { 169 | $pipelineCreated = $this->injectPipeline($config['middleware_pipeline'], $app); 170 | } 171 | 172 | if (isset($config['routes']) && is_array($config['routes'])) { 173 | $this->injectRoutes($config['routes'], $app); 174 | 175 | if (! $pipelineCreated) { 176 | $app->pipeRoutingMiddleware(); 177 | $app->pipeDispatchMiddleware(); 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Inject the middleware pipeline 184 | * 185 | * This method injects the middleware pipeline. 186 | * 187 | * If the pre-RC6 pre_/post_routing keys exist, it raises a deprecation 188 | * notice, and then builds the pipeline based on that configuration 189 | * (though it will raise an exception if other keys are *also* present). 190 | * 191 | * Otherwise, it passes the pipeline on to `injectMiddleware()`, 192 | * returning a boolean value based on whether or not any 193 | * middleware was injected. 194 | * 195 | * @deprecated This method will be removed in v1.1. 196 | * @param array $pipeline 197 | * @param Application $app 198 | * @return bool 199 | */ 200 | private function injectPipeline(array $pipeline, Application $app) 201 | { 202 | $deprecatedKeys = $this->getDeprecatedKeys(array_keys($pipeline)); 203 | if (! empty($deprecatedKeys)) { 204 | $this->handleDeprecatedPipeline($deprecatedKeys, $pipeline, $app); 205 | return true; 206 | } 207 | 208 | return $this->injectMiddleware($pipeline, $app); 209 | } 210 | 211 | /** 212 | * Retrieve a list of deprecated keys from the pipeline, if any. 213 | * 214 | * @deprecated This method will be removed in v1.1. 215 | * @param array $pipelineKeys 216 | * @return array 217 | */ 218 | private function getDeprecatedKeys(array $pipelineKeys) 219 | { 220 | return array_intersect(['pre_routing', 'post_routing'], $pipelineKeys); 221 | } 222 | 223 | /** 224 | * Handle deprecated pre_/post_routing configuration. 225 | * 226 | * @deprecated This method will be removed in v1.1. 227 | * @param array $deprecatedKeys The list of deprecated keys present in the 228 | * pipeline 229 | * @param array $pipeline 230 | * @param Application $app 231 | * @return void 232 | * @throws ContainerInvalidArgumentException if $pipeline contains more than 233 | * just pre_ and/or post_routing keys. 234 | * @throws ContainerInvalidArgumentException if the pre_routing configuration, 235 | * if present, is not an array 236 | * @throws ContainerInvalidArgumentException if the post_routing configuration, 237 | * if present, is not an array 238 | */ 239 | private function handleDeprecatedPipeline(array $deprecatedKeys, array $pipeline, Application $app) 240 | { 241 | if (count($deprecatedKeys) < count($pipeline)) { 242 | throw new ContainerInvalidArgumentException( 243 | 'middleware_pipeline cannot contain a mix of middleware AND pre_/post_routing keys; ' 244 | . 'please update your configuration to define middleware_pipeline as a single pipeline; ' 245 | . 'see https://zendframework.github.io/zend-expressive/reference/migration/rc-to-v1/' 246 | ); 247 | } 248 | 249 | trigger_error( 250 | 'pre_routing and post_routing configuration is deprecated; ' 251 | . 'update your configuration to define the middleware_pipeline as a single pipeline; ' 252 | . 'see https://zendframework.github.io/zend-expressive/reference/migration/rc-to-v1/', 253 | E_USER_DEPRECATED 254 | ); 255 | 256 | if (isset($pipeline['pre_routing'])) { 257 | if (! is_array($pipeline['pre_routing'])) { 258 | throw new ContainerInvalidArgumentException(sprintf( 259 | 'Pre-routing middleware collection must be an array; received "%s"', 260 | gettype($pipeline['pre_routing']) 261 | )); 262 | } 263 | $this->injectMiddleware($pipeline['pre_routing'], $app); 264 | } 265 | 266 | $app->pipeRoutingMiddleware(); 267 | $app->pipeRouteResultObserverMiddleware(); 268 | $app->pipeDispatchMiddleware(); 269 | 270 | if (isset($pipeline['post_routing'])) { 271 | if (! is_array($pipeline['post_routing'])) { 272 | throw new ContainerInvalidArgumentException(sprintf( 273 | 'Post-routing middleware collection must be an array; received "%s"', 274 | gettype($pipeline['post_routing']) 275 | )); 276 | } 277 | $this->injectMiddleware($pipeline['post_routing'], $app); 278 | } 279 | } 280 | 281 | /** 282 | * Inject routes from configuration, if any. 283 | * 284 | * @param array $routes Route definitions 285 | * @param Application $app 286 | */ 287 | private function injectRoutes(array $routes, Application $app) 288 | { 289 | foreach ($routes as $spec) { 290 | if (! isset($spec['path']) || ! isset($spec['middleware'])) { 291 | continue; 292 | } 293 | 294 | if (isset($spec['allowed_methods'])) { 295 | $methods = $spec['allowed_methods']; 296 | if (! is_array($methods)) { 297 | throw new ContainerInvalidArgumentException(sprintf( 298 | 'Allowed HTTP methods for a route must be in form of an array; received "%s"', 299 | gettype($methods) 300 | )); 301 | } 302 | } else { 303 | $methods = Route::HTTP_METHOD_ANY; 304 | } 305 | $name = isset($spec['name']) ? $spec['name'] : null; 306 | $route = new Route($spec['path'], $spec['middleware'], $methods, $name); 307 | 308 | if (isset($spec['options'])) { 309 | $options = $spec['options']; 310 | if (! is_array($options)) { 311 | throw new ContainerInvalidArgumentException(sprintf( 312 | 'Route options must be an array; received "%s"', 313 | gettype($options) 314 | )); 315 | } 316 | 317 | $route->setOptions($options); 318 | } 319 | 320 | $app->route($route); 321 | } 322 | } 323 | 324 | /** 325 | * Given a collection of middleware specifications, pipe them to the application. 326 | * 327 | * @param array $collection 328 | * @param Application $app 329 | * @return bool Flag indicating whether or not any middleware was injected. 330 | * @throws Exception\InvalidMiddlewareException for invalid middleware. 331 | */ 332 | private function injectMiddleware(array $collection, Application $app) 333 | { 334 | // Create a priority queue from the specifications 335 | $queue = array_reduce( 336 | array_map($this->createCollectionMapper($app), $collection), 337 | $this->createPriorityQueueReducer(), 338 | new SplPriorityQueue() 339 | ); 340 | 341 | $injections = count($queue) > 0; 342 | 343 | foreach ($queue as $spec) { 344 | $path = isset($spec['path']) ? $spec['path'] : '/'; 345 | $error = array_key_exists('error', $spec) ? (bool) $spec['error'] : false; 346 | $pipe = $error ? 'pipeErrorHandler' : 'pipe'; 347 | 348 | $app->{$pipe}($path, $spec['middleware']); 349 | } 350 | 351 | return $injections; 352 | } 353 | 354 | /** 355 | * Create and return the pipeline map callback. 356 | * 357 | * The returned callback has the signature: 358 | * 359 | * 360 | * function ($item) : callable|string 361 | * 362 | * 363 | * It is suitable for mapping pipeline middleware representing the application 364 | * routing o dispatching middleware to a callable; if the provided item does not 365 | * match either, the item is returned verbatim. 366 | * 367 | * @todo Remove ROUTE_RESULT_OBSERVER_MIDDLEWARE detection for 1.1 368 | * @param Application $app 369 | * @return callable 370 | */ 371 | private function createPipelineMapper(Application $app) 372 | { 373 | return function ($item) use ($app) { 374 | if ($item === self::ROUTING_MIDDLEWARE) { 375 | return [$app, 'routeMiddleware']; 376 | } 377 | 378 | if ($item === self::DISPATCH_MIDDLEWARE) { 379 | return [$app, 'dispatchMiddleware']; 380 | } 381 | 382 | if ($item === self::ROUTE_RESULT_OBSERVER_MIDDLEWARE) { 383 | $r = new \ReflectionProperty($app, 'routeResultObserverMiddlewareIsRegistered'); 384 | $r->setAccessible(true); 385 | $r->setValue($app, true); 386 | return [$app, 'routeResultObserverMiddleware']; 387 | } 388 | 389 | return $item; 390 | }; 391 | } 392 | 393 | /** 394 | * Create the collection mapping function. 395 | * 396 | * Returns a callable with the following signature: 397 | * 398 | * 399 | * function (array|string $item) : array 400 | * 401 | * 402 | * When it encounters one of the self::*_MIDDLEWARE constants, it passes 403 | * the value to the `createPipelineMapper()` callback to create a spec 404 | * that uses the return value as pipeline middleware. 405 | * 406 | * If the 'middleware' value is an array, it uses the `createPipelineMapper()` 407 | * callback as an array mapper in order to ensure the self::*_MIDDLEWARE 408 | * are injected correctly. 409 | * 410 | * If the 'middleware' value is missing, or not viable as middleware, it 411 | * raises an exception, to ensure the pipeline is built correctly. 412 | * 413 | * @param Application $app 414 | * @return callable 415 | */ 416 | private function createCollectionMapper(Application $app) 417 | { 418 | $pipelineMap = $this->createPipelineMapper($app); 419 | $appMiddlewares = [ 420 | self::ROUTING_MIDDLEWARE, 421 | self::DISPATCH_MIDDLEWARE, 422 | self::ROUTE_RESULT_OBSERVER_MIDDLEWARE 423 | ]; 424 | 425 | return function ($item) use ($app, $pipelineMap, $appMiddlewares) { 426 | if (in_array($item, $appMiddlewares, true)) { 427 | return ['middleware' => $pipelineMap($item)]; 428 | } 429 | 430 | if (! is_array($item) || ! array_key_exists('middleware', $item)) { 431 | throw new ContainerInvalidArgumentException(sprintf( 432 | 'Invalid pipeline specification received; must be an array containing a middleware ' 433 | . 'key, or one of the ApplicationFactory::*_MIDDLEWARE constants; received %s', 434 | (is_object($item) ? get_class($item) : gettype($item)) 435 | )); 436 | } 437 | 438 | if (! is_callable($item['middleware']) && is_array($item['middleware'])) { 439 | $item['middleware'] = array_map($pipelineMap, $item['middleware']); 440 | } 441 | 442 | return $item; 443 | }; 444 | } 445 | 446 | /** 447 | * Create reducer function that will reduce an array to a priority queue. 448 | * 449 | * Creates and returns a function with the signature: 450 | * 451 | * 452 | * function (SplQueue $queue, array $item) : SplQueue 453 | * 454 | * 455 | * The function is useful to reduce an array of pipeline middleware to a 456 | * priority queue. 457 | * 458 | * @return callable 459 | */ 460 | private function createPriorityQueueReducer() 461 | { 462 | // $serial is used to ensure that items of the same priority are enqueued 463 | // in the order in which they are inserted. 464 | $serial = PHP_INT_MAX; 465 | return function ($queue, $item) use (&$serial) { 466 | $priority = isset($item['priority']) && is_int($item['priority']) 467 | ? $item['priority'] 468 | : 1; 469 | $queue->insert($item, [$priority, $serial--]); 470 | return $queue; 471 | }; 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | router = $router; 122 | $this->container = $container; 123 | $this->finalHandler = $finalHandler; 124 | $this->emitter = $emitter; 125 | } 126 | 127 | /** 128 | * Overload middleware invocation. 129 | * 130 | * If $out is not provided, uses the result of `getFinalHandler()`. 131 | * 132 | * @todo Remove logic for creating final handler for version 2.0.0. 133 | * @todo Remove swallowDeprecationNotices() invocation for version 2.0.0. 134 | * @param ServerRequestInterface $request 135 | * @param ResponseInterface $response 136 | * @param callable|null $out 137 | * @return ResponseInterface 138 | */ 139 | public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null) 140 | { 141 | $this->swallowDeprecationNotices(); 142 | 143 | if (! $out && (null === ($out = $this->getFinalHandler($response)))) { 144 | $response = $response instanceof StratigilityResponse 145 | ? $response 146 | : new StratigilityResponse($response); 147 | $out = new FinalHandler([], $response); 148 | } 149 | 150 | $result = parent::__invoke($request, $response, $out); 151 | 152 | restore_error_handler(); 153 | 154 | return $result; 155 | } 156 | 157 | /** 158 | * @param string $method 159 | * @param array $args 160 | * @return Router\Route 161 | * @throws Exception\BadMethodCallException if the $method is not in $httpRouteMethods. 162 | * @throws Exception\BadMethodCallException if receiving more or less than 2 arguments. 163 | */ 164 | public function __call($method, $args) 165 | { 166 | if (! in_array(strtoupper($method), $this->httpRouteMethods, true)) { 167 | throw new Exception\BadMethodCallException('Unsupported method'); 168 | } 169 | 170 | switch (count($args)) { 171 | case 2: 172 | // We have path and middleware; append the HTTP method. 173 | $args[] = [$method]; 174 | break; 175 | case 3: 176 | // Need to reflow arguments to (0 => path, 1 => middleware, 2 => methods, 3 => name) 177 | // from (0 => path, 1 => middleware, 2 => name) 178 | $args[3] = $args[2]; // place name in $args[3] 179 | $args[2] = [$method]; // method becomes $args[2] 180 | break; 181 | default: 182 | throw new Exception\BadMethodCallException(sprintf( 183 | '%s::%s requires at least 2 arguments, and no more than 3; received %d', 184 | __CLASS__, 185 | $method, 186 | count($args) 187 | )); 188 | } 189 | 190 | // @TODO: we can use variadic parameters when dependency is raised to PHP 5.6 191 | return call_user_func_array([$this, 'route'], $args); 192 | } 193 | 194 | /** 195 | * @param string|Router\Route $path 196 | * @param callable|string $middleware Middleware (or middleware service name) to associate with route. 197 | * @param null|string $name the name of the route 198 | * @return Router\Route 199 | */ 200 | public function any($path, $middleware, $name = null) 201 | { 202 | return $this->route($path, $middleware, null, $name); 203 | } 204 | 205 | /** 206 | * Attach a route result observer. 207 | * 208 | * @deprecated This method will be removed in v1.1. 209 | * @param Router\RouteResultObserverInterface $observer 210 | */ 211 | public function attachRouteResultObserver(Router\RouteResultObserverInterface $observer) 212 | { 213 | $this->routeResultObservers[] = $observer; 214 | } 215 | 216 | /** 217 | * Detach a route result observer. 218 | * 219 | * @deprecated This method will be removed in v1.1. 220 | * @param Router\RouteResultObserverInterface $observer 221 | */ 222 | public function detachRouteResultObserver(Router\RouteResultObserverInterface $observer) 223 | { 224 | if (false === ($index = array_search($observer, $this->routeResultObservers, true))) { 225 | return; 226 | } 227 | unset($this->routeResultObservers[$index]); 228 | } 229 | 230 | /** 231 | * Notify all route result observers with the given route result. 232 | * 233 | * @deprecated This method will be removed in v1.1. 234 | * @param Router\RouteResult 235 | */ 236 | public function notifyRouteResultObservers(Router\RouteResult $result) 237 | { 238 | foreach ($this->routeResultObservers as $observer) { 239 | $observer->update($result); 240 | } 241 | } 242 | 243 | /** 244 | * Overload pipe() operation. 245 | * 246 | * Middleware piped may be either callables or service names. Middleware 247 | * specified as services will be wrapped in a closure similar to the 248 | * following: 249 | * 250 | * 251 | * function ($request, $response, $next = null) use ($container, $middleware) { 252 | * $invokable = $container->get($middleware); 253 | * if (! is_callable($invokable)) { 254 | * throw new Exception\InvalidMiddlewareException(sprintf( 255 | * 'Lazy-loaded middleware "%s" is not invokable', 256 | * $middleware 257 | * )); 258 | * } 259 | * return $invokable($request, $response, $next); 260 | * }; 261 | * 262 | * 263 | * This is done to delay fetching the middleware until it is actually used; 264 | * the upshot is that you will not be notified if the service is invalid to 265 | * use as middleware until runtime. 266 | * 267 | * Middleware may also be passed as an array; each item in the array must 268 | * resolve to middleware eventually (i.e., callable or service name). 269 | * 270 | * Finally, ensures that the route middleware is only ever registered 271 | * once. 272 | * 273 | * @param string|array|callable $path Either a URI path prefix, or middleware. 274 | * @param null|string|array|callable $middleware Middleware 275 | * @return self 276 | */ 277 | public function pipe($path, $middleware = null) 278 | { 279 | if (null === $middleware) { 280 | $middleware = $this->prepareMiddleware($path, $this->container); 281 | $path = '/'; 282 | } 283 | 284 | if (! is_callable($middleware) 285 | && (is_string($middleware) || is_array($middleware)) 286 | ) { 287 | $middleware = $this->prepareMiddleware($middleware, $this->container); 288 | } 289 | 290 | if ($middleware === [$this, 'routeMiddleware'] && $this->routeMiddlewareIsRegistered) { 291 | return $this; 292 | } 293 | 294 | if ($middleware === [$this, 'dispatchMiddleware'] && $this->dispatchMiddlewareIsRegistered) { 295 | return $this; 296 | } 297 | 298 | parent::pipe($path, $middleware); 299 | 300 | if ($middleware === [$this, 'routeMiddleware']) { 301 | $this->routeMiddlewareIsRegistered = true; 302 | } 303 | 304 | if ($middleware === [$this, 'dispatchMiddleware']) { 305 | $this->dispatchMiddlewareIsRegistered = true; 306 | } 307 | 308 | return $this; 309 | } 310 | 311 | /** 312 | * Pipe an error handler. 313 | * 314 | * Middleware piped may be either callables or service names. Middleware 315 | * specified as services will be wrapped in a closure similar to the 316 | * following: 317 | * 318 | * 319 | * function ($error, $request, $response, $next) use ($container, $middleware) { 320 | * $invokable = $container->get($middleware); 321 | * if (! is_callable($invokable)) { 322 | * throw new Exception\InvalidMiddlewareException(sprintf( 323 | * 'Lazy-loaded middleware "%s" is not invokable', 324 | * $middleware 325 | * )); 326 | * } 327 | * return $invokable($error, $request, $response, $next); 328 | * }; 329 | * 330 | * 331 | * This is done to delay fetching the middleware until it is actually used; 332 | * the upshot is that you will not be notified if the service is invalid to 333 | * use as middleware until runtime. 334 | * 335 | * Once middleware detection and wrapping (if necessary) is complete, 336 | * proxies to pipe(). 337 | * 338 | * @param string|callable $path Either a URI path prefix, or middleware. 339 | * @param null|string|callable $middleware Middleware 340 | * @return self 341 | */ 342 | public function pipeErrorHandler($path, $middleware = null) 343 | { 344 | if (null === $middleware) { 345 | $middleware = $this->prepareMiddleware($path, $this->container, $forError = true); 346 | $path = '/'; 347 | } 348 | 349 | if (! is_callable($middleware) 350 | && (is_string($middleware) || is_array($middleware)) 351 | ) { 352 | $middleware = $this->prepareMiddleware($middleware, $this->container, $forError = true); 353 | } 354 | 355 | parent::pipe($path, $middleware); 356 | 357 | return $this; 358 | } 359 | 360 | /** 361 | * Register the routing middleware in the middleware pipeline. 362 | */ 363 | public function pipeRoutingMiddleware() 364 | { 365 | if ($this->routeMiddlewareIsRegistered) { 366 | return; 367 | } 368 | $this->pipe([$this, 'routeMiddleware']); 369 | } 370 | 371 | /** 372 | * Register the dispatch middleware in the middleware pipeline. 373 | */ 374 | public function pipeDispatchMiddleware() 375 | { 376 | if ($this->dispatchMiddlewareIsRegistered) { 377 | return; 378 | } 379 | $this->pipe([$this, 'dispatchMiddleware']); 380 | } 381 | 382 | /** 383 | * Register the route result observer middleware in the middleware pipeline. 384 | * 385 | * @deprecated This method will be removed in v1.1. 386 | */ 387 | public function pipeRouteResultObserverMiddleware() 388 | { 389 | if ($this->routeResultObserverMiddlewareIsRegistered) { 390 | return; 391 | } 392 | $this->pipe([$this, 'routeResultObserverMiddleware']); 393 | $this->routeResultObserverMiddlewareIsRegistered = true; 394 | } 395 | 396 | /** 397 | * Middleware that routes the incoming request and delegates to the matched middleware. 398 | * 399 | * Uses the router to route the incoming request, injecting the request 400 | * with: 401 | * 402 | * - the route result object (under a key named for the RouteResult class) 403 | * - attributes for each matched routing parameter 404 | * 405 | * On completion, it calls on the next middleware (typically the 406 | * `dispatchMiddleware()`). 407 | * 408 | * If routing fails, `$next()` is called; if routing fails due to HTTP 409 | * method negotiation, the response is set to a 405, injected with an 410 | * Allow header, and `$next()` is called with its `$error` argument set 411 | * to the value `405` (invoking the next error middleware). 412 | * 413 | * @param ServerRequestInterface $request 414 | * @param ResponseInterface $response 415 | * @param callable $next 416 | * @return ResponseInterface 417 | */ 418 | public function routeMiddleware(ServerRequestInterface $request, ResponseInterface $response, callable $next) 419 | { 420 | $result = $this->router->match($request); 421 | 422 | if ($result->isFailure()) { 423 | if ($result->isMethodFailure()) { 424 | $response = $response->withStatus(405) 425 | ->withHeader('Allow', implode(',', $result->getAllowedMethods())); 426 | 427 | // Need to swallow deprecation notices, as this is how 405 errors 428 | // are reported in the 1.0 series. 429 | $this->swallowDeprecationNotices(); 430 | return $next($request, $response, 405); 431 | } 432 | return $next($request, $response); 433 | } 434 | 435 | // Inject the actual route result, as well as individual matched parameters. 436 | $request = $request->withAttribute(Router\RouteResult::class, $result); 437 | foreach ($result->getMatchedParams() as $param => $value) { 438 | $request = $request->withAttribute($param, $value); 439 | } 440 | 441 | return $next($request, $response); 442 | } 443 | 444 | /** 445 | * Dispatch the middleware matched by routing. 446 | * 447 | * If the request does not have the route result, calls on the next 448 | * middleware. 449 | * 450 | * Next, it checks if the route result has matched middleware; if not, it 451 | * raises an exception. 452 | * 453 | * Finally, it attempts to marshal the middleware, and dispatches it when 454 | * complete, return the response. 455 | * 456 | * @param ServerRequestInterface $request 457 | * @param ResponseInterface $response 458 | * @param callable $next 459 | * @returns ResponseInterface 460 | * @throws Exception\InvalidMiddlewareException if no middleware is present 461 | * to dispatch in the route result. 462 | */ 463 | public function dispatchMiddleware(ServerRequestInterface $request, ResponseInterface $response, callable $next) 464 | { 465 | $routeResult = $request->getAttribute(Router\RouteResult::class, false); 466 | if (! $routeResult) { 467 | return $next($request, $response); 468 | } 469 | 470 | $middleware = $routeResult->getMatchedMiddleware(); 471 | if (! $middleware) { 472 | throw new Exception\InvalidMiddlewareException(sprintf( 473 | 'The route %s does not have a middleware to dispatch', 474 | $routeResult->getMatchedRouteName() 475 | )); 476 | } 477 | 478 | $middleware = $this->prepareMiddleware($middleware, $this->container); 479 | return $middleware($request, $response, $next); 480 | } 481 | 482 | /** 483 | * Middleware for notifying route result observers. 484 | * 485 | * If the request has a route result, calls notifyRouteResultObservers(). 486 | * 487 | * This middleware should be injected between the routing and dispatch 488 | * middleware when creating your middleware pipeline. 489 | * 490 | * If you are using this, rewrite your observers as middleware that 491 | * pulls the route result from the request instead. 492 | * 493 | * @deprecated This method will be removed in v1.1. 494 | * @param ServerRequestInterface $request 495 | * @param ResponseInterface $response 496 | * @param callable $next 497 | * @returns ResponseInterface 498 | */ 499 | public function routeResultObserverMiddleware( 500 | ServerRequestInterface $request, 501 | ResponseInterface $response, 502 | callable $next 503 | ) { 504 | $result = $request->getAttribute(Router\RouteResult::class, false); 505 | if ($result) { 506 | $this->notifyRouteResultObservers($result); 507 | } 508 | 509 | return $next($request, $response); 510 | } 511 | 512 | /** 513 | * Add a route for the route middleware to match. 514 | * 515 | * Accepts either a Router\Route instance, or a combination of a path and 516 | * middleware, and optionally the HTTP methods allowed. 517 | * 518 | * On first invocation, pipes the route middleware to the middleware 519 | * pipeline. 520 | * 521 | * @param string|Router\Route $path 522 | * @param callable|string|array $middleware Middleware (or middleware service name) to associate with route. 523 | * @param null|array $methods HTTP method to accept; null indicates any. 524 | * @param null|string $name the name of the route 525 | * @return Router\Route 526 | * @throws Exception\InvalidArgumentException if $path is not a Router\Route AND middleware is null. 527 | */ 528 | public function route($path, $middleware = null, array $methods = null, $name = null) 529 | { 530 | if (! $path instanceof Router\Route && null === $middleware) { 531 | throw new Exception\InvalidArgumentException(sprintf( 532 | '%s expects either a route argument, or a combination of a path and middleware arguments', 533 | __METHOD__ 534 | )); 535 | } 536 | 537 | if ($path instanceof Router\Route) { 538 | $route = $path; 539 | $path = $route->getPath(); 540 | $methods = $route->getAllowedMethods(); 541 | $name = $route->getName(); 542 | } 543 | 544 | $this->checkForDuplicateRoute($path, $methods); 545 | 546 | if (! isset($route)) { 547 | $methods = (null === $methods) ? Router\Route::HTTP_METHOD_ANY : $methods; 548 | $route = new Router\Route($path, $middleware, $methods, $name); 549 | } 550 | 551 | $this->routes[] = $route; 552 | $this->router->addRoute($route); 553 | 554 | return $route; 555 | } 556 | 557 | /** 558 | * Run the application 559 | * 560 | * If no request or response are provided, the method will use 561 | * ServerRequestFactory::fromGlobals to create a request instance, and 562 | * instantiate a default response instance. 563 | * 564 | * It then will invoke itself with the request and response, and emit 565 | * the returned response using the composed emitter. 566 | * 567 | * @param null|ServerRequestInterface $request 568 | * @param null|ResponseInterface $response 569 | */ 570 | public function run(ServerRequestInterface $request = null, ResponseInterface $response = null) 571 | { 572 | $response = $response ?: new Response(); 573 | $request = $request ?: ServerRequestFactory::fromGlobals(); 574 | $request = $request->withAttribute('originalResponse', $response); 575 | 576 | $response = $this($request, $response); 577 | 578 | $emitter = $this->getEmitter(); 579 | $emitter->emit($response); 580 | } 581 | 582 | /** 583 | * Retrieve the IoC container. 584 | * 585 | * If no IoC container is registered, we raise an exception. 586 | * 587 | * @return \Interop\Container\ContainerInterface 588 | * @throws Exception\ContainerNotRegisteredException 589 | */ 590 | public function getContainer() 591 | { 592 | if (null === $this->container) { 593 | throw new Exception\ContainerNotRegisteredException(); 594 | } 595 | return $this->container; 596 | } 597 | 598 | /** 599 | * Return the final handler to use during `run()` if the stack is exhausted. 600 | * 601 | * @param null|ResponseInterface $response Response instance with which to seed the 602 | * FinalHandler; used to determine if the response passed to the handler 603 | * represents the original or final response state. 604 | * @return callable|null 605 | */ 606 | public function getFinalHandler(ResponseInterface $response = null) 607 | { 608 | if (! $this->finalHandler) { 609 | return null; 610 | } 611 | 612 | // Inject the handler with the response, if possible (e.g., the 613 | // TemplatedErrorHandler and WhoopsErrorHandler implementations). 614 | if (method_exists($this->finalHandler, 'setOriginalResponse')) { 615 | $this->finalHandler->setOriginalResponse($response); 616 | } 617 | 618 | return $this->finalHandler; 619 | } 620 | 621 | /** 622 | * Retrieve an emitter to use during run(). 623 | * 624 | * If none was registered during instantiation, this will lazy-load an 625 | * EmitterStack composing an SapiEmitter instance. 626 | * 627 | * @return EmitterInterface 628 | */ 629 | public function getEmitter() 630 | { 631 | if (! $this->emitter) { 632 | $this->emitter = new Emitter\EmitterStack(); 633 | $this->emitter->push(new SapiEmitter()); 634 | } 635 | return $this->emitter; 636 | } 637 | 638 | /** 639 | * Determine if the route is duplicated in the current list. 640 | * 641 | * Checks if a route with the same name or path exists already in the list; 642 | * if so, and it responds to any of the $methods indicated, raises 643 | * a DuplicateRouteException indicating a duplicate route. 644 | * 645 | * @param string $path 646 | * @param null|array $methods 647 | * @throws Exception\DuplicateRouteException on duplicate route detection. 648 | */ 649 | private function checkForDuplicateRoute($path, $methods = null) 650 | { 651 | if (null === $methods) { 652 | $methods = Router\Route::HTTP_METHOD_ANY; 653 | } 654 | 655 | $matches = array_filter($this->routes, function (Router\Route $route) use ($path, $methods) { 656 | if ($path !== $route->getPath()) { 657 | return false; 658 | } 659 | 660 | if ($methods === Router\Route::HTTP_METHOD_ANY) { 661 | return true; 662 | } 663 | 664 | return array_reduce($methods, function ($carry, $method) use ($route) { 665 | return ($carry || $route->allowsMethod($method)); 666 | }, false); 667 | }); 668 | 669 | if (count($matches) > 0) { 670 | throw new Exception\DuplicateRouteException( 671 | 'Duplicate route detected; same name or path, and one or more HTTP methods intersect' 672 | ); 673 | } 674 | } 675 | 676 | /** 677 | * Register an error handler to swallow deprecation notices due to error middleware usage. 678 | * 679 | * @todo Remove method for version 2.0.0. 680 | * @return void 681 | */ 682 | private function swallowDeprecationNotices() 683 | { 684 | $previous = null; 685 | $handler = function ($errno, $errstr, $errfile, $errline, $errcontext) use (&$previous) { 686 | $swallow = $errno === E_USER_DEPRECATED && false !== strstr($errstr, 'error middleware is deprecated'); 687 | 688 | if ($swallow || $previous === null) { 689 | return $swallow; 690 | } 691 | 692 | $previous($errno, $errstr, $errfile, $errline, $errcontext); 693 | }; 694 | 695 | $previous = set_error_handler($handler); 696 | } 697 | } 698 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <img type="image/svg+xml" height="300" src="ws/build/pdepend/overview-pyramid.svg" width="500"></img> 5 | <img type="image/svg+xml" height="300" src="ws/build/pdepend/dependencies.svg" width="500"></img> 6 | false 7 | 8 | 9 | 2 10 | 11 | 12 | 13 | 14 | /var/www/laravel 15 | 16 | 17 | 18 | 19 | ** 20 | 21 | 22 | false 23 | false 24 | false 25 | false 26 | false 27 | false 28 | false 29 | false 30 | false 31 | false 32 | 33 | Default 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | false 42 | 43 | 44 | 45 | true 46 | false 47 | false 48 | false 49 | 50 | 51 | 52 | 53 | 54 | false 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | low 65 | [CHECKSTYLE] 66 | 67 | true 68 | false 69 | false 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | false 89 | false 90 | false 91 | build/logs/checkstyle.xml 92 | 93 | 94 | 95 | 96 | low 97 | [PMD] 98 | 99 | true 100 | false 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | false 121 | false 122 | true 123 | build/logs/pmd.xml 124 | 125 | 126 | 127 | 128 | low 129 | [DRY] 130 | 131 | true 132 | false 133 | false 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | false 153 | false 154 | false 155 | build/logs/pmd-cpd.xml 156 | 50 157 | 25 158 | 159 | 160 | 161 | 162 | A - Lines of code 163 | Lines of Code 164 | 165 | 166 | build/logs/phploc.csv 167 | 168 | csv 169 | 170 | Lines of Code (LOC) 171 | Comment Lines of Code (CLOC) 172 | Non-Comment Lines of Code (NCLOC) 173 | 174 | INCLUDE_BY_STRING 175 | Lines of Code (LOC),Comment Lines of Code (CLOC),Non-Comment Lines of Code (NCLOC) 176 | 177 | false 178 | 179 | 180 | phploc 181 | 100 182 | 123.csv 183 | 0 184 | 185 | false 186 | 187 | 188 | B - Structures 189 | Count 190 | 191 | 192 | build/logs/phploc.csv 193 | 194 | csv 195 | 196 | Functions 197 | Classes 198 | Namespaces 199 | Files 200 | Directories 201 | Methods 202 | Interfaces 203 | Constants 204 | Anonymous Functions 205 | 206 | INCLUDE_BY_STRING 207 | Directories,Files,Namespaces,Interfaces,Classes,Methods,Functions,Anonymous Functions,Constants 208 | 209 | false 210 | 211 | 212 | phploc 213 | 100 214 | 1107599928.csv 215 | 0 216 | 217 | false 218 | 219 | 220 | G - Average Length 221 | Average Non-Comment Lines of Code 222 | 223 | 224 | build/logs/phploc.csv 225 | 226 | csv 227 | 228 | Average Method Length (NCLOC) 229 | Average Class Length (NCLOC) 230 | 231 | INCLUDE_BY_STRING 232 | Average Class Length (NCLOC),Average Method Length (NCLOC) 233 | 234 | false 235 | 236 | 237 | phploc 238 | 100 239 | 523405415.csv 240 | 0 241 | 242 | false 243 | 244 | 245 | H - Relative Cyclomatic Complexity 246 | Cyclomatic Complexity by Structure 247 | 248 | 249 | build/logs/phploc.csv 250 | 251 | csv 252 | 253 | Cyclomatic Complexity / Lines of Code 254 | Cyclomatic Complexity / Number of Methods 255 | 256 | INCLUDE_BY_STRING 257 | Cyclomatic Complexity / Lines of Code,Cyclomatic Complexity / Number of Methods 258 | 259 | false 260 | 261 | 262 | phploc 263 | 100 264 | 186376189.csv 265 | 0 266 | 267 | false 268 | 269 | 270 | D - Types of Classes 271 | Count 272 | 273 | 274 | build/logs/phploc.csv 275 | 276 | csv 277 | 278 | Abstract Classes 279 | Classes 280 | Concrete Classes 281 | 282 | INCLUDE_BY_STRING 283 | Classes,Abstract Classes,Concrete Classes 284 | 285 | false 286 | 287 | 288 | phploc 289 | 100 290 | 594356163.csv 291 | 0 292 | 293 | false 294 | 295 | 296 | E - Types of Methods 297 | Count 298 | 299 | 300 | build/logs/phploc.csv 301 | 302 | csv 303 | 304 | Methods 305 | Static Methods 306 | Non-Static Methods 307 | Public Methods 308 | Non-Public Methods 309 | 310 | INCLUDE_BY_STRING 311 | Methods,Non-Static Methods,Static Methods,Public Methods,Non-Public Methods 312 | 313 | false 314 | 315 | 316 | phploc 317 | 100 318 | 1019987862.csv 319 | 0 320 | 321 | false 322 | 323 | 324 | F - Types of Constants 325 | Count 326 | 327 | 328 | build/logs/phploc.csv 329 | 330 | csv 331 | 332 | Class Constants 333 | Global Constants 334 | Constants 335 | 336 | INCLUDE_BY_STRING 337 | Constants,Global Constants,Class Constants 338 | 339 | false 340 | 341 | 342 | phploc 343 | 100 344 | 217648577.csv 345 | 0 346 | 347 | false 348 | 349 | 350 | C - Testing 351 | Count 352 | 353 | 354 | build/logs/phploc.csv 355 | 356 | csv 357 | 358 | Functions 359 | Classes 360 | Methods 361 | Test Clases 362 | Test Methods 363 | 364 | INCLUDE_BY_STRING 365 | Classes,Methods,Functions,Test Clases,Test Methods 366 | 367 | false 368 | 369 | 370 | phploc 371 | 100 372 | 174807245.csv 373 | 0 374 | 375 | false 376 | 377 | 378 | 379 | 380 | true 381 | build/coverage 382 | build/logs/clover.xml 383 | false 384 | 385 | 70 386 | 80 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Code Browser 395 | build/code-browser 396 | index.html 397 | true 398 | htmlpublisher-wrapper.html 399 | 400 | 401 | 402 | 403 | 404 | 405 | build/logs/junit.xml 406 | true 407 | true 408 | true 409 | 410 | 411 | 412 | 413 | 0 414 | 0 415 | 0 416 | 0 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 0 423 | 424 | 425 | 1 426 | 427 | 428 | build/logs/jdepend.xml 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | checkstyle 439 | 440 | checkstyle 441 | 10 442 | 999 443 | 999 444 | false 445 | build/logs/checkstyle.xml 446 | 447 | 448 | 449 | codenarc 450 | 451 | codenarc 452 | 10 453 | 999 454 | 999 455 | false 456 | 457 | 458 | 459 | 460 | cpd 461 | 462 | cpd 463 | 10 464 | 999 465 | 999 466 | false 467 | build/logs/pmd-cpd.xml 468 | 469 | 470 | 471 | cpplint 472 | 473 | cpplint 474 | 10 475 | 999 476 | 999 477 | false 478 | 479 | 480 | 481 | 482 | csslint 483 | 484 | csslint 485 | 10 486 | 999 487 | 999 488 | false 489 | 490 | 491 | 492 | 493 | findbugs 494 | 495 | findbugs 496 | 10 497 | 999 498 | 999 499 | false 500 | 501 | 502 | 503 | 504 | fxcop 505 | 506 | fxcop 507 | 10 508 | 999 509 | 999 510 | false 511 | 512 | 513 | 514 | 515 | gendarme 516 | 517 | gendarme 518 | 10 519 | 999 520 | 999 521 | false 522 | 523 | 524 | 525 | 526 | jcreport 527 | 528 | jcreport 529 | 10 530 | 999 531 | 999 532 | false 533 | 534 | 535 | 536 | 537 | jslint 538 | 539 | jslint 540 | 10 541 | 999 542 | 999 543 | false 544 | 545 | 546 | 547 | 548 | pep8 549 | 550 | pep8 551 | 10 552 | 999 553 | 999 554 | false 555 | 556 | 557 | 558 | 559 | perlcritic 560 | 561 | perlcritic 562 | 10 563 | 999 564 | 999 565 | false 566 | 567 | 568 | 569 | 570 | pmd 571 | 572 | pmd 573 | 10 574 | 999 575 | 999 576 | false 577 | build/logs/pmd.xml 578 | 579 | 580 | 581 | pylint 582 | 583 | pylint 584 | 10 585 | 999 586 | 999 587 | false 588 | 589 | 590 | 591 | 592 | simian 593 | 594 | simian 595 | 10 596 | 999 597 | 999 598 | false 599 | 600 | 601 | 602 | 603 | stylecop 604 | 605 | stylecop 606 | 10 607 | 999 608 | 999 609 | false 610 | 611 | 612 | 613 | 614 | 100 615 | 616 | 617 | default 618 | 619 | 620 | 621 | 622 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 1.0.6 - 2017-01-09 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Deprecated 12 | 13 | - Nothing. 14 | 15 | ### Removed 16 | 17 | - Nothing. 18 | 19 | ### Fixed 20 | 21 | - [#420](https://github.com/zendframework/zend-expressive/pull/420) fixes the 22 | `routeMiddleware()`'s handling of 405 errors such that it now no longer emits 23 | deprecation notices when running under the Stratigility 1.3 series. 24 | 25 | ## 1.0.5 - 2016-12-08 26 | 27 | ### Added 28 | 29 | - Nothing. 30 | 31 | ### Deprecated 32 | 33 | - Nothing. 34 | 35 | ### Removed 36 | 37 | - Nothing. 38 | 39 | ### Fixed 40 | 41 | - [#403](https://github.com/zendframework/zend-expressive/pull/403) updates the 42 | `AppFactory::create()` logic to raise exceptions in either of the following 43 | scenarios: 44 | - no container is specified, and the class `Zend\ServiceManager\ServiceManager` 45 | is not available. 46 | - no router is specified, and the class `Zend\Expressive\Router\FastRouteRouter` 47 | is not available. 48 | - [#405](https://github.com/zendframework/zend-expressive/pull/405) fixes how 49 | the `TemplatedErrorHandler` injects templated content into the response. 50 | Previously, it would `write()` directly to the existing response body, which 51 | could lead to issues if previous middleware had written to the response (as 52 | the templated contents would append the previous contents). With this release, 53 | it now creates a new `Zend\Diactoros\Stream`, writes to that, and returns a 54 | new response with that new stream, guaranteeing it only contains the new 55 | contents. 56 | - [#404](https://github.com/zendframework/zend-expressive/pull/404) fixes the 57 | `swallowDeprecationNotices()` handler such that it will not swallow a global 58 | handler once application execution completes. 59 | 60 | ## 1.0.4 - 2016-12-07 61 | 62 | ### Added 63 | 64 | - Nothing. 65 | 66 | ### Deprecated 67 | 68 | - Nothing. 69 | 70 | ### Removed 71 | 72 | - Nothing. 73 | 74 | ### Fixed 75 | 76 | - [#402](https://github.com/zendframework/zend-expressive/pull/402) fixes how 77 | `Application::__invoke()` registers the error handler designed to swallow 78 | deprecation notices, as introduced in 1.0.3. It now checks to see if another 79 | error handler was previously registered, and, if so, creates a composite 80 | handler that will delegate to the previous for all other errors. 81 | 82 | ## 1.0.3 - 2016-11-11 83 | 84 | ### Added 85 | 86 | - Nothing. 87 | 88 | ### Changes 89 | 90 | - [#395](https://github.com/zendframework/zend-expressive/pull/395) updates 91 | `Application::__invoke()` to add an error handler to swallow deprecation 92 | notices due to triggering error middleware when using Stratigility 1.3+. Since 93 | error middleware is triggered whenever the `raiseThrowables` flag is not 94 | enabled and an error or empty queue situation is encountered, handling it this 95 | way prevents any such errors from bubbling out of the application. 96 | 97 | ### Deprecated 98 | 99 | - Nothing. 100 | 101 | ### Removed 102 | 103 | - Nothing. 104 | 105 | ### Fixed 106 | 107 | - Nothing. 108 | 109 | ## 1.0.2 - 2016-11-11 110 | 111 | ### Added 112 | 113 | - Nothing. 114 | 115 | ### Changes 116 | 117 | - [#393](https://github.com/zendframework/zend-expressive/pull/393) updates 118 | `Application::run()` to inject the request with an `originalResponse` 119 | attribute using the provided response as the value. 120 | 121 | ### Deprecated 122 | 123 | - Nothing. 124 | 125 | ### Removed 126 | 127 | - Nothing. 128 | 129 | ### Fixed 130 | 131 | - [#393](https://github.com/zendframework/zend-expressive/pull/393) fixes how 132 | each of the `TemplatedErrorHandler` and `WhoopsErrorHandler` access the 133 | "original" request, URI, and/or response. Previously, these used 134 | Stratigility-specific methods; they now use request attributes, eliminating 135 | deprecation notices emitted in Stratigility 1.3+ versions. 136 | 137 | ## 1.0.1 - 2016-11-11 138 | 139 | ### Added 140 | 141 | - [#306](https://github.com/zendframework/zend-expressive/pull/306) adds a 142 | cookbook recipe covering flash messages. 143 | - [#384](https://github.com/zendframework/zend-expressive/pull/384) adds support 144 | for Whoops version 2 releases, providing PHP 7 support for Whoops. 145 | 146 | ### Deprecated 147 | 148 | - Nothing. 149 | 150 | ### Removed 151 | 152 | - Nothing. 153 | 154 | ### Fixed 155 | 156 | - [#391](https://github.com/zendframework/zend-expressive/pull/391) fixes the 157 | `Application::run()` implementation to prevent emission of deprecation notices 158 | when used with Stratigility 1.3. 159 | 160 | ## 1.0.0 - 2016-01-28 161 | 162 | Initial stable release. 163 | 164 | ### Added 165 | 166 | - [#279](https://github.com/zendframework/zend-expressive/pull/279) updates 167 | the documentation to provide automation for pushing to GitHub pages. As part 168 | of that work, documentation was re-organized, and a landing page provided. 169 | Documentation can now be found at: https://zendframework.github.io/zend-expressive/ 170 | - [#299](https://github.com/zendframework/zend-expressive/pull/299) adds 171 | component-specific CSS to the documentation. 172 | - [#295](https://github.com/zendframework/zend-expressive/pull/295) adds 173 | support for handling PHP 7 engine exceptions in the templated and whoops final 174 | handlers. 175 | 176 | ### Deprecated 177 | 178 | - Nothing. 179 | 180 | ### Removed 181 | 182 | - Nothing. 183 | 184 | ### Fixed 185 | 186 | - [#280](https://github.com/zendframework/zend-expressive/pull/280) fixes 187 | references to the `PlatesRenderer` in the error handling documentation. 188 | - [#284](https://github.com/zendframework/zend-expressive/pull/284) fixes 189 | the reference to maximebf/php-debugbar in the debug bar documentation. 190 | - [#285](https://github.com/zendframework/zend-expressive/pull/285) updates 191 | the section on mtymek/blast-base-url in the "Using a Base Path" cookbook 192 | recipe to conform to its latest release. 193 | - [#286](https://github.com/zendframework/zend-expressive/pull/286) fixes the 194 | documentation of the Composer "serve" command to correct a typo. 195 | - [#291](https://github.com/zendframework/zend-expressive/pull/291) fixes the 196 | documentation links to the RC5 -> v1 migration guide in both the CHANGELOG as 197 | well as the error messages emitted, ensuring users can locate the correct 198 | documentation in order to upgrade. 199 | - [#287](https://github.com/zendframework/zend-expressive/pull/287) updates the 200 | "standalone" quick start to reference calling `$app->pipeRoutingMiddleware()` 201 | and `$app->pipeDispatchMiddleware()` per the changes in RC6. 202 | - [#293](https://github.com/zendframework/zend-expressive/pull/293) adds 203 | a `require 'vendor/autoload.php';` line to the bootstrap script referenced in 204 | the zend-servicemanager examples. 205 | - [#294](https://github.com/zendframework/zend-expressive/pull/294) updates the 206 | namespace referenced in the modulear-layout documentation to provide a better 207 | separation between the module/package/whatever, and the application consuming 208 | it. 209 | - [#298](https://github.com/zendframework/zend-expressive/pull/298) fixes a typo 210 | in a URI generation example. 211 | 212 | ## 1.0.0rc7 - 2016-01-21 213 | 214 | Seventh release candidate. 215 | 216 | ### Added 217 | 218 | - [#277](https://github.com/zendframework/zend-expressive/pull/277) adds a new 219 | class, `Zend\Expressive\ErrorMiddlewarePipe`. It composes a 220 | `Zend\Stratigility\MiddlewarePipe`, but implements the error middleware 221 | signature via its own `__invoke()` method. 222 | 223 | ### Deprecated 224 | 225 | - Nothing. 226 | 227 | ### Removed 228 | 229 | - Nothing. 230 | 231 | ### Fixed 232 | 233 | - [#277](https://github.com/zendframework/zend-expressive/pull/277) updates the 234 | `MarshalMiddlewareTrait` to create and return an `ErrorMiddlewarePipe` when 235 | the `$forError` argument provided indicates error middleware is expected. 236 | This fix allows defining arrays of error middleware via configuration. 237 | 238 | ## 1.0.0rc6 - 2016-01-18 239 | 240 | Sixth release candidate. 241 | 242 | This release contains backwards compatibility breaks with previous release 243 | candidates. All previous functionality should continue to work, but will 244 | emit `E_USER_DEPRECATED` notices prompting you to update your application. 245 | In particular: 246 | 247 | - The routing middleware has been split into two separate middleware 248 | implementations, one for routing, another for dispatching. This eliminates the 249 | need for the route result observer system, as middleware can now be placed 250 | *between* routing and dispatching — an approach that provides for greater 251 | flexibility with regards to providing route-based functionality. 252 | - As a result of the above, `Zend\Expressive\Application` no longer implements 253 | `Zend\Expressive\Router\RouteResultSubjectInterface`, though it retains the 254 | methods associated (each emits a deprecation notice). 255 | - Configuration for `Zend\Expressive\Container\ApplicationFactory` was modified 256 | to implement the `middleware_pipeline` as a single queue, instead of 257 | segregating it between `pre_routing` and `post_routing`. Each item in the 258 | queue follows the original middleware specification from those keys, with one 259 | addition: a `priority` key can be used to allow you to granularly shape the 260 | execution order of the middleware pipeline. 261 | 262 | A [migration guide](https://zendframework.github.io/zend-expressive/reference/migration/rc-to-v1/) 263 | was written to help developers migrate to RC6 from earlier versions. 264 | 265 | ### Added 266 | 267 | - [#255](https://github.com/zendframework/zend-expressive/pull/255) adds 268 | documentation for the base path functionality provided by the `UrlHelper` 269 | class of zend-expressive-helpers. 270 | - [#227](https://github.com/zendframework/zend-expressive/pull/227) adds 271 | a section on creating localized routes, and setting the application locale 272 | based on the matched route. 273 | - [#244](https://github.com/zendframework/zend-expressive/pull/244) adds 274 | a recipe on using middleware to detect localized URIs (vs using a routing 275 | parameter), setting the application locale based on the match detected, 276 | and setting the `UrlHelper` base path with the same match. 277 | - [#260](https://github.com/zendframework/zend-expressive/pull/260) adds 278 | a recipe on how to add debug toolbars to your Expressive applications. 279 | - [#261](https://github.com/zendframework/zend-expressive/pull/261) adds 280 | a flow/architectural diagram to the "features" chapter. 281 | - [#262](https://github.com/zendframework/zend-expressive/pull/262) adds 282 | a recipe demonstrating creating classes that can intercept multiple routes. 283 | - [#270](https://github.com/zendframework/zend-expressive/pull/270) adds 284 | new methods to `Zend\Expressive\Application`: 285 | - `dispatchMiddleware()` is new middleware for dispatching the middleware 286 | matched by routing (this functionality was split from `routeMiddleware()`). 287 | - `routeResultObserverMiddleware()` is new middleware for notifying route 288 | result observers, and exists only to aid migration functionality; it is 289 | marked deprecated! 290 | - `pipeDispatchMiddleware()` will pipe the dispatch middleware to the 291 | `Application` instance. 292 | - `pipeRouteResultObserverMiddleware()` will pipe the route result observer 293 | middleware to the `Application` instance; like 294 | `routeResultObserverMiddleware()`, the method only exists for aiding 295 | migration, and is marked deprecated. 296 | - [#270](https://github.com/zendframework/zend-expressive/pull/270) adds 297 | `Zend\Expressive\MarshalMiddlewareTrait`, which is composed by 298 | `Zend\Expressive\Application`; it provides methods for marshaling 299 | middleware based on service names or arrays of services. 300 | 301 | ### Deprecated 302 | 303 | - [#270](https://github.com/zendframework/zend-expressive/pull/270) deprecates 304 | the following methods in `Zend\Expressive\Application`, all of which will 305 | be removed in version 1.1: 306 | - `attachRouteResultObserver()` 307 | - `detachRouteResultObserver()` 308 | - `notifyRouteResultObservers()` 309 | - `pipeRouteResultObserverMiddleware()` 310 | - `routeResultObserverMiddleware()` 311 | 312 | ### Removed 313 | 314 | - [#270](https://github.com/zendframework/zend-expressive/pull/270) removes the 315 | `Zend\Expressive\Router\RouteResultSubjectInterface` implementation from 316 | `Zend\Expressive\Application`. 317 | - [#270](https://github.com/zendframework/zend-expressive/pull/270) eliminates 318 | the `pre_routing`/`post_routing` terminology from the `middleware_pipeline`, 319 | in favor of individually specified `priority` values in middleware 320 | specifications. 321 | 322 | ### Fixed 323 | 324 | - [#263](https://github.com/zendframework/zend-expressive/pull/263) typo 325 | fixes in documentation 326 | 327 | ## 1.0.0rc5 - 2015-12-22 328 | 329 | Fifth release candidate. 330 | 331 | ### Added 332 | 333 | - [#233](https://github.com/zendframework/zend-expressive/pull/233) adds a 334 | documentation page detailing projects using and tutorials written on 335 | Expressive. 336 | - [#238](https://github.com/zendframework/zend-expressive/pull/238) adds a 337 | cookbook recipe detailing how to handle serving an Expressive application from 338 | a subdirectory of your web root. 339 | - [#239](https://github.com/zendframework/zend-expressive/pull/239) adds a 340 | cookbook recipe detailing how to create modular Expressive applications. 341 | - [#243](https://github.com/zendframework/zend-expressive/pull/243) adds a 342 | chapter to the helpers section detailing the new `BodyParseMiddleware`. 343 | 344 | ### Deprecated 345 | 346 | - Nothing. 347 | 348 | ### Removed 349 | 350 | - Nothing. 351 | 352 | ### Fixed 353 | 354 | - [#234](https://github.com/zendframework/zend-expressive/pull/234) fixes the 355 | inheritance tree for `Zend\Expressive\Exception\RuntimeException` to inherit 356 | from `RuntimeException` and not `InvalidArgumentException`. 357 | - [#237](https://github.com/zendframework/zend-expressive/pull/237) updates the 358 | Pimple documentation to recommend `xtreamwayz/pimple-container-interop` 359 | instead of `mouf/pimple-interop`, as the latter consumed Pimple v1, instead of 360 | the current stable v3. 361 | 362 | ## 1.0.0rc4 - 2015-12-09 363 | 364 | Fourth release candidate. 365 | 366 | ### Added 367 | 368 | - [#217](https://github.com/zendframework/zend-expressive/pull/217) adds a 369 | cookbook entry to the documentation detailing how to configure zend-view 370 | helpers from other components, as well as how to add custom view helpers. 371 | 372 | ### Deprecated 373 | 374 | - Nothing. 375 | 376 | ### Removed 377 | 378 | - Nothing. 379 | 380 | ### Fixed 381 | 382 | - [#219](https://github.com/zendframework/zend-expressive/pull/219) updates the 383 | "Hello World Using a Configuration-Driven Container" usage case to use 384 | zend-stdlib's `Glob::glob()` instead of the `glob()` native function, to 385 | ensure the documented solution is portable across platforms. 386 | - [#223](https://github.com/zendframework/zend-expressive/pull/223) updates the 387 | documentation to refer to the `composer serve` command where relevant, and 388 | also details how to create the command for standalone users. 389 | - [#221](https://github.com/zendframework/zend-expressive/pull/221) splits the 390 | various cookbook entries into separate files, so each is self-contained. 391 | - [#224](https://github.com/zendframework/zend-expressive/pull/224) adds opening 392 | ` 1.0-dev, dev-develop => 1.1-dev. 477 | - Point dev dependencies on sub-components to `~1.0-dev`. 478 | 479 | ## 1.0.0rc1 - 2015-10-19 480 | 481 | First release candidate. 482 | 483 | ### Added 484 | 485 | - Nothing. 486 | 487 | ### Deprecated 488 | 489 | - Nothing. 490 | 491 | ### Removed 492 | 493 | - Nothing. 494 | 495 | ### Fixed 496 | 497 | - Nothing. 498 | 499 | ## 0.5.3 - 2015-10-19 500 | 501 | ### Added 502 | 503 | - Nothing. 504 | 505 | ### Deprecated 506 | 507 | - Nothing. 508 | 509 | ### Removed 510 | 511 | - Nothing. 512 | 513 | ### Fixed 514 | 515 | - [#160](https://github.com/zendframework/zend-expressive/pull/160) updates 516 | `EmitterStack` to throw a component-specific `InvalidArgumentException` 517 | instead of the generic SPL version. 518 | - [#163](https://github.com/zendframework/zend-expressive/pull/163) change the 519 | documentation on wiring middleware factories to put them in the `dependencies` 520 | section of `routes.global.php`; this keeps the routing and middleware 521 | configuration in the same file. 522 | 523 | ## 0.5.2 - 2015-10-17 524 | 525 | ### Added 526 | 527 | - [#158](https://github.com/zendframework/zend-expressive/pull/158) documents 528 | getting started via the [installer + skeleton](https://github.com/zendframework/zend-expressive-skeleton), 529 | and also documents "next steps" in terms of creating and wiring middleware 530 | when using the skeleton. 531 | 532 | ### Deprecated 533 | 534 | - Nothing. 535 | 536 | ### Removed 537 | 538 | - Nothing. 539 | 540 | ### Fixed 541 | 542 | - Nothing. 543 | 544 | ## 0.5.1 - 2015-10-13 545 | 546 | ### Added 547 | 548 | - Nothing. 549 | 550 | ### Deprecated 551 | 552 | - Nothing. 553 | 554 | ### Removed 555 | 556 | - Nothing. 557 | 558 | ### Fixed 559 | 560 | - [#156](https://github.com/zendframework/zend-expressive/pull/156) updates how 561 | the routing middleware pulls middleware from the container; in order to work 562 | with zend-servicemanager v3 and allow `has()` queries to query abstract 563 | factories, a second, boolean argument is now passed. 564 | 565 | ## 0.5.0 - 2015-10-10 566 | 567 | ### Added 568 | 569 | - Nothing. 570 | 571 | ### Deprecated 572 | 573 | - Nothing. 574 | 575 | ### Removed 576 | 577 | - [#131](https://github.com/zendframework/zend-expressive/pull/131) modifies the 578 | repository to remove the concrete router and template renderer 579 | implementations, along with any related factories; these are now in their own 580 | packages. The classes removed include: 581 | - `Zend\Expressive\Container\Template\PlatesRendererFactory` 582 | - `Zend\Expressive\Container\Template\TwigRendererFactory` 583 | - `Zend\Expressive\Container\Template\ZendViewRendererFactory` 584 | - `Zend\Expressive\Router\AuraRouter` 585 | - `Zend\Expressive\Router\FastRouteRouter` 586 | - `Zend\Expressive\Router\ZendRouter` 587 | - `Zend\Expressive\Template\PlatesRenderer` 588 | - `Zend\Expressive\Template\TwigRenderer` 589 | - `Zend\Expressive\Template\Twig\TwigExtension` 590 | - `Zend\Expressive\Template\ZendViewRenderer` 591 | - `Zend\Expressive\Template\ZendView\NamespacedPathStackResolver` 592 | - `Zend\Expressive\Template\ZendView\ServerUrlHelper` 593 | - `Zend\Expressive\Template\ZendView\UrlHelper` 594 | 595 | ### Fixed 596 | 597 | - Nothing. 598 | 599 | ## 0.4.1 - TBD 600 | 601 | ### Added 602 | 603 | - Nothing. 604 | 605 | ### Deprecated 606 | 607 | - Nothing. 608 | 609 | ### Removed 610 | 611 | - Nothing. 612 | 613 | ### Fixed 614 | 615 | - Nothing. 616 | 617 | ## 0.4.0 - 2015-10-10 618 | 619 | ### Added 620 | 621 | - [#132](https://github.com/zendframework/zend-expressive/pull/132) adds 622 | `Zend\Expressive\Router\ZendRouter`, replacing 623 | `Zend\Expressive\Router\Zf2Router`. 624 | - [#139](https://github.com/zendframework/zend-expressive/pull/139) adds: 625 | - `Zend\Expressive\Template\TemplateRendererInterface`, replacing 626 | `Zend\Expressive\Template\TemplateInterface`. 627 | - `Zend\Expressive\Template\PlatesRenderer`, replacing 628 | `Zend\Expressive\Template\Plates`. 629 | - `Zend\Expressive\Template\TwigRenderer`, replacing 630 | `Zend\Expressive\Template\Twig`. 631 | - `Zend\Expressive\Template\ZendViewRenderer`, replacing 632 | `Zend\Expressive\Template\ZendView`. 633 | - [#143](https://github.com/zendframework/zend-expressive/pull/143) adds 634 | the method `addDefaultParam($templateName, $param, $value)` to 635 | `TemplateRendererInterface`, allowing users to specify global and 636 | template-specific default parameters to use when rendering. To implement the 637 | feature, the patch also provides `Zend\Expressive\Template\DefaultParamsTrait` 638 | to simplify incorporating the feature in implementations. 639 | - [#133](https://github.com/zendframework/zend-expressive/pull/133) adds a 640 | stipulation to `Zend\Expressive\Router\RouterInterface` that `addRoute()` 641 | should *aggregate* `Route` instances only, and delay injection until `match()` 642 | and/or `generateUri()` are called; all shipped routers now follow this. This 643 | allows manipulating `Route` instances before calling `match()` or 644 | `generateUri()` — for instance, to inject options or a name. 645 | - [#133](https://github.com/zendframework/zend-expressive/pull/133) re-instates 646 | the `Route::setName()` method, as the changes to lazy-inject routes means that 647 | setting names and options after adding them to the application now works 648 | again. 649 | 650 | ### Deprecated 651 | 652 | - Nothing. 653 | 654 | ### Removed 655 | 656 | - [#132](https://github.com/zendframework/zend-expressive/pull/132) removes 657 | `Zend\Expressive\Router\Zf2Router`, renaming it to 658 | `Zend\Expressive\Router\ZendRouter`. 659 | - [#139](https://github.com/zendframework/zend-expressive/pull/139) removes: 660 | - `Zend\Expressive\Template\TemplateInterface`, renaming it to 661 | `Zend\Expressive\Template\TemplateRendererInterface`. 662 | - `Zend\Expressive\Template\Plates`, renaming it to 663 | `Zend\Expressive\Template\PlatesRenderer`. 664 | - `Zend\Expressive\Template\Twig`, renaming it to 665 | `Zend\Expressive\Template\TwigRenderer`. 666 | - `Zend\Expressive\Template\ZendView`, renaming it to 667 | `Zend\Expressive\Template\ZendViewRenderer`. 668 | 669 | ### Fixed 670 | 671 | - Nothing. 672 | 673 | ## 0.3.1 - 2015-10-09 674 | 675 | ### Added 676 | 677 | - [#149](https://github.com/zendframework/zend-expressive/pull/149) adds 678 | verbiage to the `RouterInterface::generateUri()` method, specifying that the 679 | returned URI **MUST NOT** be escaped. The `AuraRouter` implementation has been 680 | updated to internally use `generateRaw()` to follow this guideline, and retain 681 | parity with the other existing implementations. 682 | 683 | ### Deprecated 684 | 685 | - Nothing. 686 | 687 | ### Removed 688 | 689 | - Nothing. 690 | 691 | ### Fixed 692 | 693 | - [#140](https://github.com/zendframework/zend-expressive/pull/140) updates the 694 | AuraRouter to use the request method from the request object, and inject that 695 | under the `REQUEST_METHOD` server parameter key before passing the server 696 | parameters for matching. This simplifies testing. 697 | 698 | ## 0.3.0 - 2015-09-12 699 | 700 | ### Added 701 | 702 | - [#128](https://github.com/zendframework/zend-expressive/pull/128) adds 703 | container factories for each supported template implementation: 704 | - `Zend\Expressive\Container\Template\PlatesFactory` 705 | - `Zend\Expressive\Container\Template\TwigFactory` 706 | - `Zend\Expressive\Container\Template\ZendViewFactory` 707 | - [#128](https://github.com/zendframework/zend-expressive/pull/128) adds 708 | custom `url` and `serverUrl` zend-view helper implementations, to allow 709 | integration with any router and with PSR-7 URI instances. The newly 710 | added `ZendViewFactory` will inject these into the `HelperPluginManager` by 711 | default. 712 | 713 | ### Deprecated 714 | 715 | - Nothing. 716 | 717 | ### Removed 718 | 719 | - Nothing. 720 | 721 | ### Fixed 722 | 723 | - [#128](https://github.com/zendframework/zend-expressive/pull/128) fixes an 724 | expectation in the `WhoopsErrorHandler` tests to ensure the tests can run 725 | successfully. 726 | 727 | ## 0.2.1 - 2015-09-10 728 | 729 | ### Added 730 | 731 | - Nothing. 732 | 733 | ### Deprecated 734 | 735 | - Nothing. 736 | 737 | ### Removed 738 | 739 | - Nothing. 740 | 741 | ### Fixed 742 | 743 | - [#125](https://github.com/zendframework/zend-expressive/pull/125) fixes the 744 | `WhoopsErrorHandler` to ensure it pushes the "pretty page handler" into the 745 | Whoops runtime. 746 | 747 | ## 0.2.0 - 2015-09-03 748 | 749 | ### Added 750 | 751 | - [#116](https://github.com/zendframework/zend-expressive/pull/116) adds 752 | `Application::any()` to complement the various HTTP-specific routing methods; 753 | it has the same signature as `get()`, `post()`, `patch()`, et al, but allows 754 | any HTTP method. 755 | - [#120](https://github.com/zendframework/zend-expressive/pull/120) renames the 756 | router classes for easier discoverability, to better reflect their usage, and 757 | for better naming consistency. `Aura` becomes `AuraRouter`, `FastRoute` 758 | becomes `FastRouteRouter` and `Zf2` becomes `Zf2Router`. 759 | 760 | ### Deprecated 761 | 762 | - Nothing. 763 | 764 | ### Removed 765 | 766 | - [#120](https://github.com/zendframework/zend-expressive/pull/120) removes the 767 | classes `Zend\Expressive\Router\Aura`, `Zend\Expressive\Router\FastRoute`, and 768 | `Zend\Expressive\Router\Zf`, per the "Added" section above. 769 | 770 | ### Fixed 771 | 772 | - Nothing. 773 | 774 | ## 0.1.1 - 2015-09-03 775 | 776 | ### Added 777 | 778 | - [#112](https://github.com/zendframework/zend-expressive/pull/112) adds a 779 | chapter to the documentation on using Aura.Di (v3beta) with zend-expressive. 780 | 781 | ### Deprecated 782 | 783 | - Nothing. 784 | 785 | ### Removed 786 | 787 | - Nothing. 788 | 789 | ### Fixed 790 | 791 | - [#118](https://github.com/zendframework/zend-expressive/pull/118) fixes an 792 | issue whereby route options specified via configuration were not being pushed 793 | into generated `Route` instances before being passed to the underlying router. 794 | 795 | ## 0.1.0 - 2015-08-26 796 | 797 | Initial tagged release. 798 | 799 | ### Added 800 | 801 | - Everything. 802 | 803 | ### Deprecated 804 | 805 | - Nothing. 806 | 807 | ### Removed 808 | 809 | - Nothing. 810 | 811 | ### Fixed 812 | 813 | - Nothing. 814 | --------------------------------------------------------------------------------