├── README.md ├── src ├── ZendRouter │ └── ConfigProvider.php └── ZendRouter.php ├── LICENSE.md ├── composer.json └── CHANGELOG.md /README.md: -------------------------------------------------------------------------------- 1 | # zend-mvc Router Integration for Expressive 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [mezzio/mezzio-laminasrouter](https://github.com/mezzio/mezzio-laminasrouter). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-expressive-zendrouter.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-expressive-zendrouter) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-expressive-zendrouter/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-expressive-zendrouter?branch=master) 9 | 10 | Provides [ZF2's MVC router](https://github.com/zendframework/zend-mvc) 11 | integration for [zend-expressive](https://github.com/zendframework/zend-expressive). 12 | 13 | ## Installation 14 | 15 | Install this library using composer: 16 | 17 | ```bash 18 | $ composer require zendframework/zend-expressive-zendrouter 19 | ``` 20 | 21 | ## Documentation 22 | 23 | See the Expressive [ZF2 Router documentation](https://docs.zendframework.com/zend-expressive/features/router/zf2/). 24 | -------------------------------------------------------------------------------- /src/ZendRouter/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | $this->getDependencies(), 21 | ]; 22 | } 23 | 24 | public function getDependencies() : array 25 | { 26 | return [ 27 | 'aliases' => [ 28 | RouterInterface::class => ZendRouter::class, 29 | ], 30 | 'invokables' => [ 31 | ZendRouter::class => ZendRouter::class, 32 | ], 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2018, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-expressive-zendrouter", 3 | "description": "zend-mvc router support for Expressive", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "expressive", 7 | "http", 8 | "middleware", 9 | "psr", 10 | "psr-7", 11 | "zf", 12 | "zendframework", 13 | "zend-expressive" 14 | ], 15 | "support": { 16 | "issues": "https://github.com/zendframework/zend-expressive-zendrouter/issues", 17 | "source": "https://github.com/zendframework/zend-expressive-zendrouter", 18 | "rss": "https://github.com/zendframework/zend-expressive-zendrouter/releases.atom", 19 | "slack": "https://zendframework-slack.herokuapp.com", 20 | "forum": "https://discourse.zendframework.com/c/questions/expressive" 21 | }, 22 | "require": { 23 | "php": "^7.1", 24 | "fig/http-message-util": "^1.1.2", 25 | "psr/http-message": "^1.0.1", 26 | "zendframework/zend-expressive-router": "^3.0", 27 | "zendframework/zend-psr7bridge": "^0.2.2 || ^1.0.0", 28 | "zendframework/zend-router": "^3.3.0" 29 | }, 30 | "require-dev": { 31 | "malukenho/docheader": "^0.1.6", 32 | "phpunit/phpunit": "^7.0.2", 33 | "zendframework/zend-coding-standard": "~1.0.0", 34 | "zendframework/zend-i18n": "^2.7.4", 35 | "zendframework/zend-stratigility": "^3.0" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Zend\\Expressive\\Router\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "ZendTest\\Expressive\\Router\\": "test/" 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "extra": { 51 | "branch-alias": { 52 | "dev-master": "3.0.x-dev", 53 | "dev-develop": "3.1.x-dev" 54 | }, 55 | "zf": { 56 | "config-provider": "Zend\\Expressive\\Router\\ZendRouter\\ConfigProvider" 57 | } 58 | }, 59 | "scripts": { 60 | "check": [ 61 | "@license-check", 62 | "@cs-check", 63 | "@test" 64 | ], 65 | "cs-check": "phpcs", 66 | "cs-fix": "phpcbf", 67 | "test": "phpunit --colors=always", 68 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", 69 | "license-check": "docheader check src/ test/" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /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 | ## 3.0.2 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 3.0.1 - 2019-06-18 28 | 29 | ### Added 30 | 31 | - [#46](https://github.com/zendframework/zend-expressive-zendrouter/pull/46) adds support for PHP 7.3 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - Nothing. 48 | 49 | ## 3.0.0 - 2018-03-15 50 | 51 | ### Added 52 | 53 | - [#30](https://github.com/zendframework/zend-expressive-zendrouter/pull/30) and 54 | [#35](https://github.com/zendframework/zend-expressive-zendrouter/pull/35) add 55 | support for the zend-expressive-router 3.0 series. 56 | 57 | - [#34](https://github.com/zendframework/zend-expressive-zendrouter/pull/34) 58 | adds `Zend\Expressive\Router\ZendRouter\ConfigProvider` and exposes it as a 59 | config provider within the package definition. 60 | 61 | ### Changed 62 | 63 | - [#41](https://github.com/zendframework/zend-expressive-zendrouter/pull/41) 64 | updates the minimum supported version of zend-expressive-router to 3.0.0rc3 65 | and later. 66 | 67 | ### Deprecated 68 | 69 | - Nothing. 70 | 71 | ### Removed 72 | 73 | - [#30](https://github.com/zendframework/zend-expressive-zendrouter/pull/30) 74 | removes support for the zend-expressive-router 2.0 series. 75 | 76 | - [#30](https://github.com/zendframework/zend-expressive-zendrouter/pull/30) 77 | removes support for PHP 5.6 and PHP 7.0. 78 | 79 | ### Fixed 80 | 81 | - [#37](https://github.com/zendframework/zend-expressive-zendrouter/pull/37) 82 | fixes an issue with how a failure result is marshaled when the path patches 83 | but the request method does not. The package now correctly aggregates allowed 84 | methods for the route result failure instance. 85 | 86 | - [#40](https://github.com/zendframework/zend-expressive-zendrouter/pull/40) 87 | fixes how the router creates a `RouteResult` when the path matches, but not 88 | the HTTP method. In particular, it does not provide special handling for 89 | `HEAD` requests, treating them like any other method mismatch. 90 | 91 | ## 2.2.0 - 2018-03-08 92 | 93 | ### Added 94 | 95 | - Nothing. 96 | 97 | ### Changed 98 | 99 | - [#42](https://github.com/zendframework/zend-expressive-zendrouter/pull/42) 100 | updates the minimum supported version of zend-expressive-router to 2.4.0. 101 | 102 | ### Deprecated 103 | 104 | - Nothing. 105 | 106 | ### Removed 107 | 108 | - Nothing. 109 | 110 | ### Fixed 111 | 112 | - Nothing. 113 | 114 | ## 2.1.0 - 2017-12-06 115 | 116 | ### Added 117 | 118 | - [#26](https://github.com/zendframework/zend-expressive-zendrouter/pull/26) 119 | adds support for PHP 7.2. 120 | 121 | - [#27](https://github.com/zendframework/zend-expressive-zendrouter/pull/27) 122 | adds support for the zend-psr7bridge 1.0 series of releases. 123 | 124 | ### Deprecated 125 | 126 | - Nothing. 127 | 128 | ### Removed 129 | 130 | - [#26](https://github.com/zendframework/zend-expressive-zendrouter/pull/26) 131 | removes support for HHVM. 132 | 133 | ### Fixed 134 | 135 | - Nothing. 136 | 137 | ## 2.0.1 - 2017-03-01 138 | 139 | ### Added 140 | 141 | - Nothing. 142 | 143 | ### Deprecated 144 | 145 | - Nothing. 146 | 147 | ### Removed 148 | 149 | - Nothing. 150 | 151 | ### Fixed 152 | 153 | - [#20](https://github.com/zendframework/zend-expressive-zendrouter/pull/20) 154 | fixes an import statement in `ZendRouter` to ensure the correct exception 155 | namespace is used. 156 | 157 | ## 2.0.0 - 2017-01-11 158 | 159 | ### Added 160 | 161 | - [#16](https://github.com/zendframework/zend-expressive-zendrouter/pull/16) 162 | adds support for zend-expressive-router 2.0. This includes a breaking change 163 | to those _extending_ `Zend\Expressive\Router\ZendRouter`, as the 164 | `generateUri()` method now expects a third, optional argument, 165 | `array $options = []`. 166 | 167 | For consumers, this represents new functionality; you may now pass router 168 | options, such as a translator and/or translation text domain, via the new 169 | argument when generating a URI. 170 | 171 | ### Deprecated 172 | 173 | - Nothing. 174 | 175 | ### Removed 176 | 177 | - Nothing. 178 | 179 | ### Fixed 180 | 181 | - Nothing. 182 | 183 | ## 1.3.0 - 2016-12-14 184 | 185 | ### Added 186 | 187 | - Nothing. 188 | 189 | ### Changed 190 | 191 | - [#12](https://github.com/zendframework/zend-expressive-zendrouter/pull/12) 192 | updates the zend-expressive-router dependency to 1.3.2+ 193 | 194 | - [#12](https://github.com/zendframework/zend-expressive-zendrouter/pull/12) 195 | updates the router to compose the `Zend\Expressive\Router\Route` instance 196 | associated with a successful route match in the returned `RouteResult`. This 197 | allows you to access other route metadata like the path, allowed HTTP methods, 198 | and route options. 199 | 200 | - [#12](https://github.com/zendframework/zend-expressive-zendrouter/pull/12) 201 | updates the router to always support `HEAD` and `OPTIONS` requests made to any 202 | valid route. Dispatchers will need to check if such requests are supported 203 | explicitly or implicitly by the matched route (using `Route::implicitHead()` 204 | and `Route::implicitOptions()`). 205 | 206 | ### Deprecated 207 | 208 | - Nothing. 209 | 210 | ### Removed 211 | 212 | - Nothing. 213 | 214 | ### Fixed 215 | 216 | - Nothing. 217 | 218 | ## 1.2.0 - 2016-08-11 219 | 220 | ### Added 221 | 222 | - Nothing. 223 | 224 | ### Deprecated 225 | 226 | - Nothing. 227 | 228 | ### Removed 229 | 230 | - This release removes support for PHP 5.5. 231 | 232 | ### Fixed 233 | 234 | - [#7](https://github.com/zendframework/zend-expressive-zendrouter/pull/7) 235 | updates the zend-router dependency to `^3.0`; this also required changing 236 | which routes and routers are imported internally to use the new namespace 237 | introduced in that version. The changes should have no effect on existing 238 | code, except that they will result in dependency updates. 239 | 240 | ## 1.1.0 - 2016-03-09 241 | 242 | ### Added 243 | 244 | - Nothing. 245 | 246 | ### Deprecated 247 | 248 | - Nothing. 249 | 250 | ### Removed 251 | 252 | - Nothing. 253 | 254 | ### Fixed 255 | 256 | - [#6](https://github.com/zendframework/zend-expressive-zendrouter/pull/6) 257 | updates the component to depend on zend-router instead of zend-mvc. 258 | 259 | ## 1.0.1 - 2016-01-04 260 | 261 | ### Added 262 | 263 | - Nothing. 264 | 265 | ### Deprecated 266 | 267 | - Nothing. 268 | 269 | ### Removed 270 | 271 | - Nothing. 272 | 273 | ### Fixed 274 | 275 | - [#3](https://github.com/zendframework/zend-expressive-zendrouter/pull/3) fixes 276 | an issue whereby appending a trailing slash to a route that did not define one 277 | was resulting in a 405 instead of a 404 error. 278 | 279 | ## 1.0.0 - 2015-12-07 280 | 281 | First stable release. 282 | 283 | ### Added 284 | 285 | - Nothing. 286 | 287 | ### Deprecated 288 | 289 | - Nothing. 290 | 291 | ### Removed 292 | 293 | - Nothing. 294 | 295 | ### Fixed 296 | 297 | - Nothing. 298 | 299 | ## 0.3.0 - 2015-12-02 300 | 301 | ### Added 302 | 303 | - Nothing. 304 | 305 | ### Deprecated 306 | 307 | - Nothing. 308 | 309 | ### Removed 310 | 311 | - Nothing. 312 | 313 | ### Fixed 314 | 315 | - Updated to use [zendframework/zend-expressive-router](https://github.com/zendframework/zend-expressive-router) 316 | instead of zendframework/zend-expressive. 317 | 318 | ## 0.2.0 - 2015-10-20 319 | 320 | ### Added 321 | 322 | - Nothing. 323 | 324 | ### Deprecated 325 | 326 | - Nothing. 327 | 328 | ### Removed 329 | 330 | - Nothing. 331 | 332 | ### Fixed 333 | 334 | - Updated to zend-expressive RC1. 335 | - Added branch alias for dev-master, pointing to 1.0-dev. 336 | 337 | ## 0.1.0 - 2015-10-10 338 | 339 | Initial release. 340 | 341 | ### Added 342 | 343 | - Nothing. 344 | 345 | ### Deprecated 346 | 347 | - Nothing. 348 | 349 | ### Removed 350 | 351 | - Nothing. 352 | 353 | ### Fixed 354 | 355 | - Nothing. 356 | -------------------------------------------------------------------------------- /src/ZendRouter.php: -------------------------------------------------------------------------------- 1 | createRouter(); 81 | } 82 | 83 | $this->zendRouter = $router; 84 | } 85 | 86 | public function addRoute(Route $route) : void 87 | { 88 | $this->routesToInject[] = $route; 89 | } 90 | 91 | public function match(PsrRequest $request) : RouteResult 92 | { 93 | // Must inject routes prior to matching. 94 | $this->injectRoutes(); 95 | 96 | $zendRequest = Psr7ServerRequest::toZend($request, true); 97 | $match = $this->zendRouter->match($zendRequest); 98 | 99 | if (null === $match) { 100 | // No route matched at all; to indicate that it's not due to the 101 | // request method, we specify any request method was allowed. 102 | return RouteResult::fromRouteFailure(Route::HTTP_METHOD_ANY); 103 | } 104 | 105 | return $this->marshalSuccessResultFromRouteMatch($match); 106 | } 107 | 108 | public function generateUri(string $name, array $substitutions = [], array $options = []) : string 109 | { 110 | // Must inject routes prior to generating URIs. 111 | $this->injectRoutes(); 112 | 113 | if (! $this->zendRouter->hasRoute($name)) { 114 | throw new Exception\RuntimeException(sprintf( 115 | 'Cannot generate URI based on route "%s"; route not found', 116 | $name 117 | )); 118 | } 119 | 120 | $name = isset($this->routeNameMap[$name]) ? $this->routeNameMap[$name] : $name; 121 | 122 | $options = array_merge($options, [ 123 | 'name' => $name, 124 | 'only_return_path' => true, 125 | ]); 126 | 127 | return $this->zendRouter->assemble($substitutions, $options); 128 | } 129 | 130 | private function createRouter() : TreeRouteStack 131 | { 132 | return new TreeRouteStack(); 133 | } 134 | 135 | /** 136 | * Create a successful RouteResult from the given RouteMatch. 137 | */ 138 | private function marshalSuccessResultFromRouteMatch(RouteMatch $match) : RouteResult 139 | { 140 | $params = $match->getParams(); 141 | 142 | if (array_key_exists(self::METHOD_NOT_ALLOWED_ROUTE, $params)) { 143 | return RouteResult::fromRouteFailure( 144 | $this->allowedMethodsByPath[$params[self::METHOD_NOT_ALLOWED_ROUTE]] 145 | ); 146 | } 147 | 148 | $routeName = $this->getMatchedRouteName($match->getMatchedRouteName()); 149 | 150 | $route = array_reduce($this->routes, function ($matched, $route) use ($routeName) { 151 | if ($matched) { 152 | return $matched; 153 | } 154 | 155 | // We store the route name already, so we can match on that 156 | if ($routeName === $route->getName()) { 157 | return $route; 158 | } 159 | 160 | return false; 161 | }, false); 162 | 163 | if (! $route) { 164 | // This should never happen, as Zend\Expressive\Router\Route always 165 | // ensures a non-empty route name. Marking as failed route to be 166 | // consistent with other implementations. 167 | return RouteResult::fromRouteFailure(Route::HTTP_METHOD_ANY); 168 | } 169 | 170 | return RouteResult::fromRoute($route, $params); 171 | } 172 | 173 | /** 174 | * Create route configuration for matching one or more HTTP methods. 175 | */ 176 | private function createHttpMethodRoute(Route $route) : array 177 | { 178 | return [ 179 | 'type' => 'method', 180 | 'options' => [ 181 | 'verb' => implode(',', $route->getAllowedMethods()), 182 | 'defaults' => [ 183 | 'middleware' => $route->getMiddleware(), 184 | ], 185 | ], 186 | ]; 187 | } 188 | 189 | /** 190 | * Create the configuration for the "method not allowed" route. 191 | * 192 | * The specification is used for routes that have HTTP method negotiation; 193 | * essentially, this is a route that will always match, but *after* the 194 | * HTTP method route has already failed. By checking for this route later, 195 | * we can return a 405 response with the allowed methods. 196 | */ 197 | private function createMethodNotAllowedRoute(string $path) : array 198 | { 199 | return [ 200 | 'type' => 'regex', 201 | 'priority' => -1, 202 | 'options' => [ 203 | 'regex' => '', 204 | 'defaults' => [ 205 | self::METHOD_NOT_ALLOWED_ROUTE => $path, 206 | ], 207 | 'spec' => '', 208 | ], 209 | ]; 210 | } 211 | 212 | /** 213 | * Calculate the route name. 214 | * 215 | * Routes will generally match the child HTTP method routes, which will not 216 | * match the names they were registered with; this method strips the method 217 | * route name if present. 218 | */ 219 | private function getMatchedRouteName(string $name) : string 220 | { 221 | // Check for /GET:POST style route names; if so, strip off the 222 | // child route matching the method. 223 | if (preg_match('/(?P.+)\/([!#$%&\'*+.^_`\|~0-9a-z-]+:?)+$/i', $name, $matches)) { 224 | return $matches['name']; 225 | } 226 | 227 | // Otherwise, just use the name. 228 | return rtrim($name, '/'); 229 | } 230 | 231 | /** 232 | * Inject any unprocessed routes into the underlying router implementation. 233 | */ 234 | private function injectRoutes() : void 235 | { 236 | foreach ($this->routesToInject as $index => $route) { 237 | $this->injectRoute($route); 238 | $this->routes[] = $route; 239 | unset($this->routesToInject[$index]); 240 | } 241 | } 242 | 243 | /** 244 | * Inject route into the underlying router implemetation. 245 | */ 246 | private function injectRoute(Route $route) : void 247 | { 248 | $name = $route->getName(); 249 | $path = $route->getPath(); 250 | $options = $route->getOptions(); 251 | $options = array_replace_recursive($options, [ 252 | 'route' => $path, 253 | 'defaults' => [ 254 | 'middleware' => $route->getMiddleware(), 255 | ], 256 | ]); 257 | 258 | $allowedMethods = $route->getAllowedMethods(); 259 | if (Route::HTTP_METHOD_ANY === $allowedMethods) { 260 | $this->zendRouter->addRoute($name, [ 261 | 'type' => 'segment', 262 | 'options' => $options, 263 | ]); 264 | $this->routeNameMap[$name] = $name; 265 | return; 266 | } 267 | 268 | // Remove the middleware from the segment route in favor of method route 269 | unset($options['defaults']['middleware']); 270 | if (empty($options['defaults'])) { 271 | unset($options['defaults']); 272 | } 273 | 274 | $httpMethodRouteName = implode(':', $allowedMethods); 275 | $httpMethodRoute = $this->createHttpMethodRoute($route); 276 | $methodNotAllowedRoute = $this->createMethodNotAllowedRoute($path); 277 | 278 | $spec = [ 279 | 'type' => 'segment', 280 | 'options' => $options, 281 | 'may_terminate' => false, 282 | 'child_routes' => [ 283 | $httpMethodRouteName => $httpMethodRoute, 284 | self::METHOD_NOT_ALLOWED_ROUTE => $methodNotAllowedRoute, 285 | ] 286 | ]; 287 | 288 | if (array_key_exists($path, $this->allowedMethodsByPath)) { 289 | $allowedMethods = array_merge($this->allowedMethodsByPath[$path], $allowedMethods); 290 | // Remove the method not allowed route as it is already present for the path 291 | unset($spec['child_routes'][self::METHOD_NOT_ALLOWED_ROUTE]); 292 | } 293 | 294 | $this->zendRouter->addRoute($name, $spec); 295 | $this->allowedMethodsByPath[$path] = $allowedMethods; 296 | $this->routeNameMap[$name] = sprintf('%s/%s', $name, $httpMethodRouteName); 297 | } 298 | } 299 | --------------------------------------------------------------------------------