├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── balance.php ├── composer.json ├── docs └── zh_CN │ └── readme.md ├── phpunit.xml.dist ├── src ├── Exceptions │ ├── RouteException.php │ └── RouteNotFoundException.php ├── Route.php ├── RouteCollection.php ├── RouteDispatcher.php ├── RouteMiddleware.php └── RouteRegex.php └── tests ├── RouteCollectionTest.php ├── RouteDispatcherTest.php ├── RouteMiddlewareTest.php ├── RouteRegexTest.php ├── RouteTest.php └── middleware ├── AfterMiddleware.php ├── BeforeMiddleware.php ├── BreakerMiddleware.php ├── DefaultMiddleware.php └── GlobalMiddleware.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .idea 3 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.6' 4 | - '7' 5 | - '7.1' 6 | 7 | install: composer install -vvv 8 | 9 | script: vendor/bin/phpunit 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 janhuang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastD Routing 2 | 3 | [![Build Status](https://travis-ci.org/fastdlabs/routing.svg?branch=master)](https://travis-ci.org/fastdlabs/routing) 4 | [![Latest Stable Version](https://poser.pugx.org/fastd/routing/v/stable)](https://packagist.org/packages/fastd/routing) 5 | [![Total Downloads](https://poser.pugx.org/fastd/routing/downloads)](https://packagist.org/packages/fastd/routing) 6 | [![Latest Unstable Version](https://poser.pugx.org/fastd/routing/v/unstable)](https://packagist.org/packages/fastd/routing) 7 | [![License](https://poser.pugx.org/fastd/routing/license)](https://packagist.org/packages/fastd/routing) 8 | 9 | Simple PHP router that supports routing nesting, dynamic routing, fuzzy routing, middleware, and more. Relies on the [http](https://github.com/JanHuang/http) component. 10 | 11 | ## Claim 12 | 13 | * PHP 7.2 14 | 15 | ## Composer 16 | 17 | ``` 18 | Composer require "fastd/routing" 19 | ``` 20 | 21 | ## Use 22 | 23 | You can set the route through the `RouteCollection` object, or you can create a route through the route list. Detailed documentation: [fastd/routing](docs/zh_CN/readme.md) 24 | 25 | ### Static routing 26 | 27 | ```php 28 | use FastD\Http\ServerRequest; 29 | use FastD\Routing\RouteCollection; 30 | 31 | $collection = new RouteCollection(); 32 | 33 | $collection->addRoute('GET', '/', function () { 34 |     return 'hello world'; 35 | }); 36 | 37 | $route = $collection->match(new ServerRequest('GET', '/')); // \FastD\Routing\Route 38 | 39 | echo call_user_func_array($route->getCallback(), []); 40 | ``` 41 | 42 | Route matching does not call the callback of the route, but returns the entire Route for callback processing, so `match` only returns the matching route object. 43 | 44 | ### Dynamic routing 45 | 46 | ```php 47 | use FastD\Http\ServerRequest; 48 | use FastD\Routing\RouteCollection; 49 | 50 | $collection = new RouteCollection(); 51 | 52 | $collection->addRoute('GET', '/{name}', function ($name) { 53 |     return 'hello ' . $name; 54 | }); 55 | 56 | $route = $collection->match(new ServerRequest('GET', '/foo')); // \FastD\Routing\Route 57 | 58 | echo call_user_func_array($route->getCallback(), $route->getParameters()); 59 | ``` 60 | 61 | Under dynamic routing, the successfully matched route will update the matching parameters to `getParameters`, and get the matching parameter information through `getParameters`. 62 | 63 | ### Same route, multiple methods 64 | 65 | ```php 66 | $collection = new FastD\Routing\RouteCollection(); 67 | $collection->get('/', function () { 68 |     return 'hello GET'; 69 | }); 70 | $collection->post('/', function () { 71 |     return 'hello POST'; 72 | }); 73 | $response = $collection->dispatch('GET', '/'); // hello GET 74 | $response = $collection->dispatch('POST', '/'); // hello POST 75 | ``` 76 | 77 | ### Hybrid routing 78 | 79 | In many cases, our route may only be one parameter difference. Here is an example. 80 | 81 | ```php 82 | use FastD\Http\ServerRequest; 83 | use FastD\Routing\RouteCollection; 84 | 85 | $collection = new RouteCollection(); 86 | 87 | $collection->addRoute('GET', '/{name}', function () { 88 |     return 'get1'; 89 | }); 90 | 91 | $collection->addRoute('GET', '/', function () { 92 |     return 'get2'; 93 | }); 94 | 95 | $route = $collection->match(new ServerRequest('GET', '/abc')); // \FastD\Routing\Route 96 | $route2 = $collection->match(new ServerRequest('GET', '/')); // \FastD\Routing\Route 97 | echo call_user_func_array($route->getCallback(), $route->getParameters()); 98 | echo call_user_func_array($route2->getCallback(), $route2->getParameters()); 99 | ``` 100 | 101 | ### Routing Group 102 | 103 | The routing group will add its own routing prefix to each of your sub-routing dollars, supporting multiple levels of nesting. 104 | 105 | ```php 106 | use FastD\Http\ServerRequest; 107 | use FastD\Routing\RouteCollection; 108 | 109 | $collection = new RouteCollection(); 110 | 111 | $collection->group('/v1', function (RouteCollection $collection) { 112 |     $collection->addRoute('GET', '/{name}', function () { 113 |         return 'get1'; 114 |     }); 115 | }); 116 | 117 | $route = $collection->match(new ServerRequest('GET', '/v1/abc')); 118 | 119 | echo call_user_func_array($route->getCallback(), $route->getParameters()); 120 | ``` 121 | 122 | ### Fuzzy routing 123 | 124 | The inspiration for fuzzy routing comes from the onRequest callback of the Swoole http server. Because each route entry passes onRequest, when it is created, there may be some special routes processed according to pathinfo. Then the fuzzy route can be sent. It’s time to use. 125 | 126 | ```php 127 | use FastD\Http\ServerRequest; 128 | use FastD\Routing\RouteCollection; 129 | 130 | $collection = new RouteCollection(); 131 | 132 | $collection->addRoute('GET', '/api/*', function ($path) { 133 |     return $path; 134 | }); 135 | 136 | $route = $collection->match(new ServerRequest('GET', '/api/abc')); 137 | echo call_user_func_array($route->getCallback(), $route->getParameters()); // /abc 138 | 139 | $route = $collection->match(new ServerRequest('GET', '/api/cba')); 140 | echo call_user_func_array($route->getCallback(), $route->getParameters()); // /cba 141 | ``` 142 | 143 | Match all legal routes that start with `/api` and then callback 144 | 145 | ### Routing middleware 146 | 147 | The routing component implements routing middleware based on [Http] (https://github.com/JanHuang/http) and [HTTP Middlewares] (https://github.com/JanHuang/middleware). 148 | 149 | > Routing middleware callbacks automatically call back `Psr\Http\Message\ServerRequestInterface` and `FastD\Middleware\DelegateInterface` as arguments. 150 | 151 | After the middleware call is completed, the `\Psr\Http\Message\ResponseInterface` object is returned for the program to process the output. 152 | 153 | ```php 154 | use FastD\Http\ServerRequest; 155 | use FastD\Routing\RouteCollection; 156 | 157 | $collection = new RouteCollection(); 158 | 159 | $collection->addRoute('GET', '/api/*', function (ServerRequest $request) { 160 |     return 'hello'; 161 | }); 162 | 163 | $dispatcher = new \FastD\Routing\RouteDispatcher($collection); 164 | 165 | $response = $dispatcher->dispatch(new ServerRequest('GET', '/api/abc')); 166 | 167 | echo $response->getBody(); 168 | ``` 169 | 170 | ## Testing 171 | 172 | ``` 173 | phpunit 174 | ``` 175 | 176 | ### Contribution 177 | 178 | I am very pleased to be interested and willing to participate in the creation of a better PHP ecosystem, the developer of the Swoole Eco. 179 | 180 | If you are happy with this, but don't know how to get started, you can try the following things: 181 | 182 | * Problems encountered in your system [Feedback] (https://github.com/JanHuang/fastD/issues). 183 | * Have better suggestions? Feel free to contact [bboyjanhuang@gmail.com] (mailto:bboyjanhuang@gmail.com) or [Sina Weibo: Coding Man] (http://weibo.com/ecbboyjan). 184 | 185 | ### Contact 186 | 187 | If you encounter problems during use, please contact: [bboyjanhuang@gmail.com](mailto:bboyjanhuang@gmail.com). Weibo: [Coding Man] (http://weibo.com/ecbboyjan) 188 | 189 | ## License MIT 190 | -------------------------------------------------------------------------------- /balance.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2016 6 | * 7 | * @link https://www.github.com/janhuang 8 | * @link http://www.fast-d.cn/ 9 | */ 10 | 11 | include __DIR__ . '/vendor/autoload.php'; 12 | 13 | $request = new \FastD\Http\ServerRequest('GET', '/g/30000'); 14 | 15 | for ($n = 0; $n < 100; $n++) { 16 | $nRoutes = 1000; 17 | $nMatches = 300; 18 | 19 | $router = new \FastD\Routing\RouteCollection(); 20 | 21 | $startTime = microtime(true); 22 | for ($i = 0, $str = 'a'; $i < $nRoutes; $i++, $str++) { 23 | $router->addRoute('GET', '/' . $str . '/{arg}', function () { 24 | return 'hello world'; 25 | }); 26 | } 27 | for ($i = 0; $i < $nMatches; $i++) { 28 | $res = $router->match($request); 29 | } 30 | printf("FastD first route: %f\n", microtime(true) - $startTime); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastd/routing", 3 | "description": "FastD routing component", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "routing" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "janhuang", 12 | "email": "bboyjanhuang@gmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.6", 17 | "fastd/middleware": "^1.0", 18 | "fastd/http": "^3.0" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "^5.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "FastD\\Routing\\": "src" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "": "tests/middleware" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/zh_CN/readme.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | FastD Routing 提供简单、易用和快速的 PHP 路由体验,可用于大部分 HTTP 请求路由场景。 4 | 5 | ## License MIT 6 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Exceptions/RouteException.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing\Exceptions; 11 | 12 | use RuntimeException; 13 | 14 | /** 15 | * Class RouteException 16 | * @package FastD\Routing\Exceptions 17 | */ 18 | class RouteException extends RuntimeException 19 | { 20 | 21 | } -------------------------------------------------------------------------------- /src/Exceptions/RouteNotFoundException.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing\Exceptions; 11 | 12 | /** 13 | * Class RouteNotFoundException 14 | * 15 | * @package FastD\Routing 16 | */ 17 | class RouteNotFoundException extends RouteException 18 | { 19 | /** 20 | * RouteNotFoundException constructor. 21 | * 22 | * @param string $path 23 | */ 24 | public function __construct($path) 25 | { 26 | parent::__construct(sprintf('Route "%s" is not found.', $path), 404, null); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Route.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing; 11 | 12 | 13 | /** 14 | * Class Route 15 | * 16 | * @package FastD\Routing 17 | */ 18 | class Route extends RouteRegex 19 | { 20 | /** 21 | * @var array 22 | */ 23 | protected $parameters = []; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $method = 'GET'; 29 | 30 | /** 31 | * @var mixed 32 | */ 33 | protected $callback; 34 | 35 | /** 36 | * @var array 37 | */ 38 | protected $hosts = []; 39 | 40 | /** 41 | * @var array 42 | */ 43 | protected $middleware = []; 44 | 45 | /** 46 | * Route constructor. 47 | * 48 | * @param string $method 49 | * @param $path 50 | * @param $callback 51 | * @param array $hosts 52 | */ 53 | public function __construct($method, $path, $callback, $hosts = []) 54 | { 55 | parent::__construct($path); 56 | 57 | $this->withMethod($method); 58 | 59 | $this->withCallback($callback); 60 | 61 | if(!empty($hosts)){ 62 | $this->withHosts($hosts); 63 | } 64 | } 65 | 66 | /** 67 | * @param array|string $method 68 | * @return $this 69 | */ 70 | public function withMethod($method) 71 | { 72 | $this->method = $method; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getMethod() 81 | { 82 | return $this->method; 83 | } 84 | 85 | /** 86 | * @param array|string $hosts 87 | * @return $this 88 | */ 89 | public function withHosts($hosts) 90 | { 91 | $this->hosts = $hosts; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getHosts() 100 | { 101 | return $this->hosts; 102 | } 103 | 104 | /** 105 | * @param string $callback 106 | * @return $this 107 | */ 108 | public function withCallback($callback = null) 109 | { 110 | $this->callback = $callback; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * @return \Closure 117 | */ 118 | public function getCallback() 119 | { 120 | return $this->callback; 121 | } 122 | 123 | /** 124 | * @return array 125 | */ 126 | public function getParameters() 127 | { 128 | return $this->parameters; 129 | } 130 | 131 | /** 132 | * @param array $parameters 133 | * @return $this 134 | */ 135 | public function withParameters(array $parameters) 136 | { 137 | $this->parameters = $parameters; 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * @param array $parameters 144 | * @return $this 145 | */ 146 | public function mergeParameters(array $parameters) 147 | { 148 | $this->parameters = array_merge($this->parameters, array_filter($parameters)); 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * @param $middleware 155 | * @return $this 156 | */ 157 | public function withMiddleware($middleware) 158 | { 159 | $this->middleware = [$middleware]; 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * @param $middleware 166 | * @return $this 167 | */ 168 | public function withAddMiddleware($middleware) 169 | { 170 | if (is_array($middleware)) { 171 | $this->middleware = array_merge($this->middleware, $middleware); 172 | } else { 173 | $this->middleware[] = $middleware; 174 | } 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * @return array 181 | */ 182 | public function getMiddleware() 183 | { 184 | return $this->middleware; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/RouteCollection.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing; 11 | 12 | 13 | use FastD\Routing\Exceptions\RouteNotFoundException; 14 | use Psr\Http\Message\ServerRequestInterface; 15 | 16 | /** 17 | * Class RouteCollection 18 | * 19 | * @package FastD\Routing 20 | */ 21 | class RouteCollection 22 | { 23 | const ROUTES_CHUNK = 10; 24 | 25 | /** 26 | * @var array 27 | */ 28 | protected $with = []; 29 | 30 | /** 31 | * @var array 32 | */ 33 | protected $middleware = []; 34 | 35 | /** 36 | * @var Route 37 | */ 38 | protected $activeRoute; 39 | 40 | /** 41 | * @var Route[] 42 | */ 43 | public $staticRoutes = []; 44 | 45 | /** 46 | * @var Route[] 47 | */ 48 | public $dynamicRoutes = []; 49 | 50 | /** 51 | * @var array 52 | */ 53 | public $aliasMap = []; 54 | 55 | /** 56 | * @var int 57 | */ 58 | protected $num = 1; 59 | 60 | /** 61 | * 路由分组计数器 62 | * 63 | * @var int 64 | */ 65 | protected $index = 0; 66 | 67 | /** 68 | * @var array 69 | */ 70 | protected $regexes = []; 71 | 72 | /** 73 | * @var string 74 | */ 75 | protected $namespace; 76 | 77 | /** 78 | * RouteCollection constructor. 79 | * @param null $namespace 80 | */ 81 | public function __construct($namespace = null) 82 | { 83 | $this->namespace = $namespace; 84 | } 85 | 86 | /** 87 | * @param $path 88 | * @param callable $callback 89 | * @return RouteCollection 90 | */ 91 | public function group($path, callable $callback) 92 | { 93 | $middleware = $this->middleware; 94 | if (is_array($path)) { 95 | $middlewareOptions = isset($path['middleware']) ? $path['middleware'] : []; 96 | if (is_array($middlewareOptions)) { 97 | $this->middleware = array_merge($this->middleware, $middlewareOptions); 98 | } else { 99 | $this->middleware[] = $middlewareOptions; 100 | } 101 | $path = isset($path['prefix']) ? $path['prefix'] : ''; 102 | } 103 | 104 | array_push($this->with, $path); 105 | 106 | $callback($this); 107 | 108 | array_pop($this->with); 109 | $this->middleware = $middleware; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @param $middleware 116 | * @param callable $callback 117 | * @return RouteCollection 118 | */ 119 | public function middleware($middleware, callable $callback) 120 | { 121 | array_push($this->middleware, $middleware); 122 | 123 | $callback($this); 124 | 125 | array_pop($this->middleware); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * @param $callback 132 | * @return string 133 | */ 134 | protected function concat($callback) 135 | { 136 | return !is_string($callback) ? $callback : $this->namespace . $callback; 137 | } 138 | 139 | /** 140 | * @param $path 141 | * @param $callback 142 | * @param array $defaults 143 | * @param $hosts 144 | * @return Route 145 | */ 146 | public function get($path, $callback, $hosts = [], array $defaults = []) 147 | { 148 | return $this->addRoute('GET', $path, $this->concat($callback), $hosts); 149 | } 150 | 151 | /** 152 | * @param $path 153 | * @param $callback 154 | * @param array $defaults 155 | * @param $hosts 156 | * @return Route 157 | */ 158 | public function post($path, $callback, $hosts = [], array $defaults = []) 159 | { 160 | return $this->addRoute('POST', $path, $this->concat($callback), $hosts); 161 | } 162 | 163 | /** 164 | * @param $path 165 | * @param $callback 166 | * @param array $defaults 167 | * @param $hosts 168 | * @return Route 169 | */ 170 | public function put($path, $callback, $hosts = [], array $defaults = []) 171 | { 172 | return $this->addRoute('PUT', $path, $this->concat($callback), $hosts); 173 | } 174 | 175 | /** 176 | * @param $path 177 | * @param $callback 178 | * @param array $defaults 179 | * @param $hosts 180 | * @return Route 181 | */ 182 | public function delete($path, $callback, $hosts = [], array $defaults = []) 183 | { 184 | return $this->addRoute('DELETE', $path, $this->concat($callback), $hosts); 185 | } 186 | 187 | /** 188 | * @param $path 189 | * @param $callback 190 | * @param array $defaults 191 | * @param $hosts 192 | * @return Route 193 | */ 194 | public function head($path, $callback, $hosts = [], array $defaults = []) 195 | { 196 | return $this->addRoute('HEAD', $path, $this->concat($callback), $hosts); 197 | } 198 | 199 | /** 200 | * @param $path 201 | * @param $callback 202 | * @param array $defaults 203 | * @param $hosts 204 | * @return Route 205 | */ 206 | public function options($path, $callback, $hosts = [], array $defaults = []) 207 | { 208 | return $this->addRoute('OPTIONS', $path, $this->concat($callback), $hosts); 209 | } 210 | 211 | /** 212 | * @param $path 213 | * @param $callback 214 | * @param array $defaults 215 | * @param $hosts 216 | * @return Route 217 | */ 218 | public function patch($path, $callback, $hosts = [], array $defaults = []) 219 | { 220 | return $this->addRoute('PATCH', $path, $this->concat($callback), $hosts); 221 | } 222 | 223 | /** 224 | * @param $name 225 | * @return bool|Route 226 | */ 227 | public function getRoute($name) 228 | { 229 | foreach ($this->aliasMap as $method => $routes) { 230 | if (isset($routes[$name])) { 231 | return $routes[$name]; 232 | } 233 | } 234 | 235 | return false; 236 | } 237 | 238 | /** 239 | * @return Route 240 | */ 241 | public function getActiveRoute() 242 | { 243 | return $this->activeRoute; 244 | } 245 | 246 | /** 247 | * @param $method 248 | * @param $path 249 | * @param $callback 250 | * @param $hosts 251 | * @return Route 252 | */ 253 | public function createRoute($method, $path, $callback, $hosts) 254 | { 255 | return new Route($method, $path, $callback, $hosts); 256 | } 257 | 258 | /** 259 | * @param $method 260 | * @param $path 261 | * @param $callback 262 | * @param $hosts 263 | * @return Route 264 | */ 265 | public function addRoute($method, $path, $callback, $hosts) 266 | { 267 | if (is_array($path)) { 268 | $name = $path['name']; 269 | $path = implode('/', $this->with) . $path['path']; 270 | } else { 271 | $name = $path = implode('/', $this->with) . $path; 272 | } 273 | 274 | $hostsList = (array)$hosts; 275 | 276 | if (isset($this->aliasMap[$method][$name])) { 277 | foreach ($this->aliasMap[$method][$name] as $route) { 278 | if (in_array($route->getHosts(), $hostsList)) { 279 | return $route; 280 | } 281 | } 282 | } 283 | 284 | $route = $this->createRoute($method, $path, $callback, $hostsList); 285 | $route->withAddMiddleware($this->middleware); 286 | 287 | if ($route->isStatic()) { 288 | $this->staticRoutes[$method][$path][] = $route; 289 | } else { 290 | $this->dynamicRoutes[$method][] = $route; 291 | } 292 | 293 | $this->aliasMap[$method][$name][] = $route; 294 | 295 | return $route; 296 | } 297 | 298 | /** 299 | * @param ServerRequestInterface $serverRequest 300 | * @return Route 301 | * @throws RouteNotFoundException 302 | */ 303 | public function match(ServerRequestInterface $serverRequest) 304 | { 305 | $method = $serverRequest->getMethod(); 306 | $path = $serverRequest->getUri()->getPath(); 307 | $host = $serverRequest->getUri()->getHost(); 308 | 309 | if (isset($this->staticRoutes[$method][$path])) { 310 | foreach ($this->staticRoutes[$method][$path] as $route) { 311 | if (in_array($host, $route->getHosts())) { 312 | return $this->activeRoute = $route; 313 | } 314 | } 315 | foreach ($this->staticRoutes[$method][$path] as $route) { 316 | if (empty($route->getHosts())) { 317 | return $this->activeRoute = $route; 318 | } 319 | } 320 | 321 | } else { 322 | $possiblePath = $path; 323 | if ('/' === substr($possiblePath, -1)) { 324 | $possiblePath = rtrim($possiblePath, '/'); 325 | } else { 326 | $possiblePath .= '/'; 327 | } 328 | if (isset($this->staticRoutes[$method][$possiblePath])) { 329 | foreach ($this->staticRoutes[$method][$possiblePath] as $route) { 330 | if ( 331 | empty($route->getHosts()) 332 | || in_array($host, $route->getHosts()) 333 | ) { 334 | return $this->activeRoute = $route; 335 | } 336 | } 337 | } 338 | unset($possiblePath); 339 | } 340 | 341 | if ( 342 | !isset($this->dynamicRoutes[$method]) 343 | || false === $route = $this->matchDynamicRoute($serverRequest, $method, $path, $host) 344 | ) { 345 | throw new RouteNotFoundException($path); 346 | } 347 | 348 | return $this->activeRoute = $route; 349 | } 350 | 351 | /** 352 | * @param ServerRequestInterface $serverRequest 353 | * @param string $method 354 | * @param string $path 355 | * @param string $host 356 | * @return bool|Route 357 | */ 358 | protected function matchDynamicRoute(ServerRequestInterface $serverRequest, $method, $path, $host) 359 | { 360 | foreach ($this->dynamicRoutes[$method] as $route) { 361 | if (!preg_match('~^' . $route->getRegex() . '$~', $path, $matches)) { 362 | continue; 363 | } 364 | 365 | $match = array_slice($matches, 1, count($route->getVariables())); 366 | $attributes = array_combine($route->getVariables(), $match); 367 | $attributes = array_filter($attributes); 368 | $route->mergeParameters($attributes); 369 | foreach ($route->getParameters() as $key => $attribute) { 370 | $serverRequest->withAttribute($key, $attribute); 371 | } 372 | 373 | return $route; 374 | } 375 | 376 | return false; 377 | } 378 | 379 | /** 380 | * @param $name 381 | * @param array $parameters 382 | * @param string $format 383 | * @return string 384 | * @throws \Exception 385 | */ 386 | public function generateUrl($name, array $parameters = [], $format = '') 387 | { 388 | if (false === ($route = $this->getRoute($name))) { 389 | throw new RouteNotFoundException($name); 390 | } 391 | 392 | if (!empty($format)) { 393 | $format = '.' . $format; 394 | } else { 395 | $format = ''; 396 | } 397 | 398 | if ($route->isStaticRoute()) { 399 | return $route->getPath() . $format; 400 | } 401 | 402 | $parameters = array_merge($route->getParameters(), $parameters); 403 | $queryString = []; 404 | 405 | foreach ($parameters as $key => $parameter) { 406 | if (!in_array($key, $route->getVariables())) { 407 | $queryString[$key] = $parameter; 408 | unset($parameters[$key]); 409 | } 410 | } 411 | 412 | $search = array_map(function ($v) { 413 | return '{' . $v . '}'; 414 | }, array_keys($parameters)); 415 | 416 | $replace = $parameters; 417 | 418 | $path = str_replace($search, $replace, $route->getPath()); 419 | 420 | if (false !== strpos($path, '[')) { 421 | $path = str_replace(['[', ']'], '', $path); 422 | $path = rtrim(preg_replace('~(({.*?}))~', '', $path), '/'); 423 | } 424 | 425 | return $path . $format . ([] === $queryString ? '' : '?' . http_build_query($queryString)); 426 | } 427 | } -------------------------------------------------------------------------------- /src/RouteDispatcher.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing; 11 | 12 | use Exception; 13 | use FastD\Http\ServerRequest; 14 | use FastD\Middleware\Delegate; 15 | use FastD\Middleware\Dispatcher; 16 | use FastD\Middleware\MiddlewareInterface; 17 | use FastD\Routing\Exceptions\RouteException; 18 | use Psr\Http\Message\ServerRequestInterface; 19 | 20 | /** 21 | * Class RouteDispatcher 22 | * @package FastD\Routing 23 | */ 24 | class RouteDispatcher extends Dispatcher 25 | { 26 | /** 27 | * @var RouteCollection 28 | */ 29 | protected $routeCollection; 30 | 31 | /** 32 | * @var array 33 | */ 34 | protected $definition = []; 35 | 36 | /** 37 | * @var array 38 | */ 39 | protected $appendMiddleware = []; 40 | 41 | /** 42 | * RouteDispatcher constructor. 43 | * 44 | * @param RouteCollection $routeCollection 45 | * @param $definition 46 | */ 47 | public function __construct(RouteCollection $routeCollection, $definition = []) 48 | { 49 | $this->routeCollection = $routeCollection; 50 | 51 | $this->definition = $definition; 52 | 53 | parent::__construct([]); 54 | } 55 | 56 | /** 57 | * @param $name 58 | * @param $middleware 59 | * @return RouteDispatcher 60 | */ 61 | public function addDefinition($name, $middleware) 62 | { 63 | if (isset($this->definition[$name])) { 64 | if (is_array($this->definition[$name])) { 65 | $this->definition[$name][] = $middleware; 66 | } else { 67 | $this->definition[$name] = [ 68 | $this->definition[$name], 69 | $middleware, 70 | ]; 71 | } 72 | } else { 73 | $this->definition[$name] = $middleware; 74 | } 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @return array 81 | */ 82 | public function getDefinition() 83 | { 84 | return $this->definition; 85 | } 86 | 87 | /** 88 | * @return RouteCollection 89 | */ 90 | public function getRouteCollection() 91 | { 92 | return $this->routeCollection; 93 | } 94 | 95 | /** 96 | * @param ServerRequestInterface $request 97 | * @return \Psr\Http\Message\ResponseInterface 98 | * @throws Exception 99 | */ 100 | public function dispatch(ServerRequestInterface $request) 101 | { 102 | $route = $this->routeCollection->match($request); 103 | 104 | foreach ($this->appendMiddleware as $middleware) { 105 | $route->withAddMiddleware($middleware); 106 | } 107 | 108 | return $this->callMiddleware($route, $request); 109 | } 110 | 111 | /** 112 | * @param Route $route 113 | * @param ServerRequestInterface $request 114 | * @return \Psr\Http\Message\ResponseInterface 115 | * @throws Exception 116 | */ 117 | public function callMiddleware(Route $route, ServerRequestInterface $request) 118 | { 119 | $prototypeStack = clone $this->stack; 120 | 121 | foreach ($route->getMiddleware() as $middleware) { 122 | if ($middleware instanceof MiddlewareInterface) { 123 | $prototypeStack->push($middleware); 124 | } else { 125 | if (is_string($middleware)) { 126 | if (class_exists($middleware)) { 127 | $prototypeStack->push(new $middleware); 128 | } elseif (isset($this->definition[$middleware])) { 129 | $definition = $this->definition[$middleware]; 130 | if (is_array($definition)) { 131 | foreach ($definition as $value) { 132 | $prototypeStack->push(is_string($value) ? new $value : $value); 133 | } 134 | } else { 135 | $prototypeStack->push(is_string($definition) ? new $definition : $definition); 136 | } 137 | } else { 138 | throw new \RuntimeException(sprintf('Middleware %s is not defined.', $middleware)); 139 | } 140 | } else { 141 | throw new RouteException(sprintf('Don\'t support %s middleware', gettype($middleware))); 142 | } 143 | } 144 | } 145 | 146 | // wrapper route middleware 147 | $prototypeStack->push(new RouteMiddleware($route)); 148 | 149 | try { 150 | $response = $this->PrototypeDispatch($prototypeStack, $request); 151 | unset($prototypeStack); 152 | } catch (\Throwable $exception) { 153 | unset($prototypeStack); 154 | throw $exception; 155 | } 156 | 157 | return $response; 158 | } 159 | 160 | private function PrototypeDispatch(\SplStack $stack, $request) { 161 | $response = $this->PrototypeResolve($stack)->process($request); 162 | 163 | return $response; 164 | } 165 | 166 | private function PrototypeResolve(\SplStack $stack) { 167 | return $stack->isEmpty() ? 168 | new Delegate( 169 | function () { 170 | throw new LogicException('unresolved request: middleware stack exhausted with no result'); 171 | } 172 | ) : 173 | new Delegate( 174 | function (ServerRequestInterface $request) use ($stack) { 175 | return $stack->shift()->handle($request, $this->PrototypeResolve($stack)); 176 | } 177 | ); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /src/RouteMiddleware.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing; 11 | 12 | 13 | use FastD\Http\Response; 14 | use FastD\Middleware\DelegateInterface; 15 | use FastD\Middleware\Middleware; 16 | use Psr\Http\Message\ResponseInterface; 17 | use Psr\Http\Message\ServerRequestInterface; 18 | 19 | /** 20 | * Class RouteMiddleware 21 | * @package FastD\Routing 22 | */ 23 | class RouteMiddleware extends Middleware 24 | { 25 | /** 26 | * @var Route 27 | */ 28 | protected $route; 29 | 30 | /** 31 | * RouteMiddleware constructor. 32 | * @param Route $route 33 | */ 34 | public function __construct(Route $route) 35 | { 36 | $this->route = $route; 37 | } 38 | 39 | /** 40 | * @param ServerRequestInterface $request 41 | * @param DelegateInterface $next 42 | * @return ResponseInterface 43 | */ 44 | public function handle(ServerRequestInterface $request, DelegateInterface $next = null) 45 | { 46 | if (is_string(($callback = $this->route->getCallback()))) { 47 | if (false !== strpos($callback, '@')) { 48 | list($class, $method) = explode('@', $callback); 49 | } else { 50 | $class = $callback; 51 | $method = 'handle'; 52 | } 53 | $response = call_user_func_array([new $class, $method], [$request, $next]); 54 | } else { 55 | if (is_callable($callback)) { 56 | $response = call_user_func_array($callback, [$request, $next]); 57 | } else { 58 | if (is_array($callback)) { 59 | $class = $callback[0]; 60 | if (is_string($class)) { 61 | $class = new $class; 62 | } 63 | $response = call_user_func_array([$class, $callback[1]], [$request, $next]); 64 | } else { 65 | $response = new Response('Don\'t support callback, Please setting callable function or class@method.'); 66 | } 67 | } 68 | } 69 | unset($callback); 70 | 71 | return $response; 72 | } 73 | } -------------------------------------------------------------------------------- /src/RouteRegex.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | namespace FastD\Routing; 11 | 12 | 13 | /** 14 | * Class RouteRegex 15 | * 16 | * @package FastD\Routing 17 | */ 18 | class RouteRegex 19 | { 20 | const VARIABLE_REGEX = <<<'REGEX' 21 | \{ 22 | ([a-zA-Z0-9_?*]*) 23 | (?: 24 | :([^{}]*(?:\{(?-1)\}[^{}]*)*) 25 | )? 26 | \} 27 | REGEX; 28 | 29 | const DEFAULT_DISPATCH_REGEX = '[^/]+'; 30 | const DEFAULT_OPTIONAL_REGEX = '[^/]*'; 31 | 32 | /** 33 | * @var array 34 | */ 35 | protected $variables = []; 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected $requirements = []; 41 | 42 | /** 43 | * @var string 44 | */ 45 | protected $regex; 46 | 47 | /** 48 | * @var null 49 | */ 50 | protected $path; 51 | 52 | /** 53 | * @var bool 54 | */ 55 | protected $isStatic = true; 56 | 57 | /** 58 | * RouteRegex constructor. 59 | * 60 | * @param string $path 61 | */ 62 | public function __construct($path) 63 | { 64 | $this->parseRoute($path); 65 | } 66 | 67 | /** 68 | * @return bool 69 | */ 70 | public function isStatic() 71 | { 72 | return $this->isStatic; 73 | } 74 | 75 | /** 76 | * @return array 77 | */ 78 | public function getRequirements() 79 | { 80 | return $this->requirements; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getRegex() 87 | { 88 | return $this->regex; 89 | } 90 | 91 | /** 92 | * @return array 93 | */ 94 | public function getVariables() 95 | { 96 | return $this->variables; 97 | } 98 | 99 | /** 100 | * @return null 101 | */ 102 | public function getPath() 103 | { 104 | return $this->path; 105 | } 106 | 107 | /** 108 | * @param string $path 109 | * @return string 110 | */ 111 | protected function parseRoute($path) 112 | { 113 | if ('/' !== $path) { 114 | $path = rtrim($path, '/'); 115 | } 116 | 117 | $this->path = $path; 118 | 119 | if ('*' !== substr($path, -1) && false === strpos($path, '{')) { 120 | return $this->path; 121 | } 122 | 123 | if ('*' === substr($this->path, -1)) { 124 | $this->isStatic = false; 125 | $requirement = '([\/_a-zA-Z0-9-]+){1,}'; 126 | $this->regex = str_replace('/*', $requirement, $this->path); 127 | $this->variables = [ 128 | 'path' 129 | ]; 130 | $this->requirements = [ 131 | 'path' => $requirement, 132 | ]; 133 | unset($requirement); 134 | return $this->regex; 135 | } 136 | 137 | $this->isStatic = false; 138 | 139 | if (preg_match_all('~' . self::VARIABLE_REGEX . '~x', $path, $matches, PREG_SET_ORDER)) { 140 | foreach ($matches as $match) { 141 | $path = str_replace($match[0], '(' . (isset($match[2]) ? $match[2] : static::DEFAULT_DISPATCH_REGEX) . ')', $path); 142 | $this->variables[] = $match[1]; 143 | $this->requirements[$match[1]] = isset($match[2]) ? $match[2] : static::DEFAULT_DISPATCH_REGEX; 144 | } 145 | } 146 | 147 | $this->regex = str_replace(['[(', '+)]'], ['?(', '*)'], $path) . '/?'; 148 | 149 | unset($matches, $path); 150 | 151 | return $this->regex; 152 | } 153 | } -------------------------------------------------------------------------------- /tests/RouteCollectionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2016 8 | * 9 | * @link https://www.github.com/janhuang 10 | * @link http://www.fast-d.cn/ 11 | */ 12 | class RouteCollectionTest extends PHPUnit_Framework_TestCase 13 | { 14 | public function testNamespace() 15 | { 16 | $collection = new RouteCollection('\\Controller\\'); 17 | $collection->get('/', 'IndexController@welcome'); 18 | $route = $collection->getRoute('/'); 19 | $this->assertEquals('\\Controller\\IndexController@welcome', $route->getCallback()); 20 | } 21 | 22 | public function testMiddleware() 23 | { 24 | $collection = new RouteCollection(); 25 | $collection->middleware('cors', function (RouteCollection $router) { 26 | $router->get('/', 'IndexController@welcome'); 27 | }); 28 | $this->assertEquals(['cors'], $collection->getRoute('/')->getMiddleware()); 29 | 30 | $collection->get('/welcome', 'IndexController@welcome'); 31 | $this->assertEmpty($collection->getRoute('/welcome')->getMiddleware()); 32 | } 33 | 34 | public function testGroup() 35 | { 36 | $collection = new RouteCollection(); 37 | $collection->group([ 38 | 'middleware' => 'test1', 39 | ], function ($router) { 40 | $router->get('/', 'Demo@Demo')->withAddMiddleware('test'); 41 | }); 42 | } 43 | 44 | public function testRouteName() 45 | { 46 | $collection = new RouteCollection(); 47 | $collection->get([ 48 | 'name' => 'demo', 49 | 'path' => '/', 50 | ], 'IndexController@welcome'); 51 | 52 | $route = $collection->getRoute('demo'); 53 | $this->assertEquals('/', $route->getPath()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/RouteDispatcherTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | use FastD\Http\Response; 11 | use FastD\Http\ServerRequest; 12 | use FastD\Routing\RouteCollection; 13 | use FastD\Routing\RouteDispatcher; 14 | 15 | class RouteDispatcherTest extends PHPUnit_Framework_TestCase 16 | { 17 | /** 18 | * @throws Exception 19 | */ 20 | public function testDefaultDispatcher() 21 | { 22 | $routeCollection = new RouteCollection(); 23 | $routeCollection->get('/', [$this, 'response']); 24 | $dispatcher = new RouteDispatcher($routeCollection); 25 | $response = $dispatcher->dispatch(new ServerRequest('GET', '/')); 26 | $this->assertEquals(200, (string)$response->getStatusCode()); 27 | $this->assertEquals('hello world', (string)$response->getBody()); 28 | } 29 | 30 | public function testAppendMiddleware() 31 | { 32 | $routeCollection = new RouteCollection(); 33 | $routeCollection->get('/', [$this, 'response']); 34 | $dispatcher = new RouteDispatcher($routeCollection); 35 | $response = $dispatcher->dispatch(new ServerRequest('GET', '/')); 36 | $this->assertEquals(200, (string)$response->getStatusCode()); 37 | $this->assertEquals('hello world', (string)$response->getBody()); 38 | } 39 | 40 | public function response() 41 | { 42 | return new Response('hello world'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/RouteMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @see https://www.github.com/janhuang 7 | * @see http://www.fast-d.cn/ 8 | */ 9 | 10 | use FastD\Http\Response; 11 | use FastD\Http\ServerRequest; 12 | use FastD\Middleware\Delegate; 13 | use FastD\Routing\Route; 14 | use FastD\Routing\RouteMiddleware; 15 | 16 | class RouteMiddlewareTest extends PHPUnit_Framework_TestCase 17 | { 18 | protected $response; 19 | 20 | protected function response() 21 | { 22 | if (null === $this->response) { 23 | $this->response = new Response(); 24 | } 25 | 26 | return $this->response; 27 | } 28 | 29 | public function testRouteMiddleware() 30 | { 31 | $middleware = new RouteMiddleware(new Route('GET', '/', function (ServerRequest $request) { 32 | return $this->response()->withContent('hello'); 33 | })); 34 | 35 | $response = $middleware->handle(new ServerRequest('GET', '/'), new Delegate(function () { 36 | 37 | })); 38 | 39 | echo $response->getBody(); 40 | $this->expectOutputString('hello'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/RouteRegexTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | use FastD\Routing\RouteRegex; 11 | 12 | class RouteRegexTest extends PHPUnit_Framework_TestCase 13 | { 14 | public function testRegex() 15 | { 16 | $regex = new RouteRegex('/test/{name:\d+}/[{age}]'); 17 | $this->assertRegExp('~^(' . $regex->getRegex() . ')$~', '/test/18'); 18 | $this->assertEquals(['name', 'age'], $regex->getVariables()); 19 | $this->assertEquals([ 20 | 'name' => '\d+', 21 | 'age' => '[^/]+' 22 | ], $regex->getRequirements()); 23 | } 24 | 25 | public function testMatchingLastCharset() 26 | { 27 | $regex = new RouteRegex('/[{name}]/'); 28 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/foo'); 29 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/foo'); 30 | 31 | $regex = new RouteRegex('/{name}'); 32 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/foo'); 33 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/foo/'); 34 | } 35 | 36 | public function testFuzzyMatchingRoute() 37 | { 38 | $regex = new RouteRegex('/*'); 39 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/test/18'); 40 | 41 | $regex = new RouteRegex('/abc/*'); 42 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/abc/foo/bar'); 43 | 44 | $regex = new RouteRegex('/foo/*'); 45 | $this->assertRegExp('~^' . $regex->getRegex() . '$~', '/foo/foo/bar'); 46 | } 47 | 48 | public function testRouteStaticOrDynamic() 49 | { 50 | $regex = new RouteRegex('/test/{name:\d+}/[{age}]'); 51 | $this->assertFalse($regex->isStatic()); 52 | 53 | $regex = new RouteRegex('/foo'); 54 | $this->assertTrue($regex->isStatic()); 55 | } 56 | 57 | public function testRegexVariables() 58 | { 59 | $regex = new RouteRegex('/test/{name:\d+}/[{age}]'); 60 | $this->assertEquals([ 61 | 'name', 'age' 62 | ], $regex->getVariables()); 63 | } 64 | 65 | public function testRegexRequirements() 66 | { 67 | $regex = new RouteRegex('/test/{name:\d+}/[{age}]'); 68 | $this->assertEquals([ 69 | 'name' => '\d+', 70 | 'age' => '[^/]+' 71 | ], $regex->getRequirements()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/RouteTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * 6 | * @link https://www.github.com/janhuang 7 | * @link http://www.fast-d.cn/ 8 | */ 9 | 10 | use FastD\Routing\Route; 11 | 12 | class RouteTest extends PHPUnit_Framework_TestCase 13 | { 14 | public function testStaticRoute() 15 | { 16 | $route = new Route('GET', '/test', []); 17 | $this->assertEquals('GET', $route->getMethod()); 18 | $this->assertEquals('/test', $route->getPath()); 19 | $this->assertEmpty($route->getParameters()); 20 | $this->assertNull($route->getRegex()); 21 | } 22 | 23 | public function testDynamicRouteRequireVariables() 24 | { 25 | $route = new Route('GET', '/users/{name}', []); 26 | $regex = '~^(' . $route->getRegex() . ')$~'; 27 | $this->assertRegExp($regex, '/users/10'); 28 | } 29 | 30 | public function testRouteIsStatic() 31 | { 32 | $route = new Route('GET', '/foo/*', []); 33 | $this->assertFalse($route->isStatic()); 34 | } 35 | 36 | public function testRouteFuzzyMatching() 37 | { 38 | $route = new Route('GET', '/foo/*', []); 39 | $regex = '~^(' . $route->getRegex() . ')$~'; 40 | $this->assertRegExp($regex, '/foo/10'); 41 | $this->assertRegExp($regex, '/foo/bar'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/middleware/AfterMiddleware.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2016 6 | * 7 | * @link https://www.github.com/janhuang 8 | * @link http://www.fast-d.cn/ 9 | */ 10 | class AfterMiddleware extends \FastD\Middleware\Middleware 11 | { 12 | /** 13 | * @param \Psr\Http\Message\ServerRequestInterface $serverRequest 14 | * @param \FastD\Middleware\DelegateInterface $delegate 15 | * @return \Psr\Http\Message\ResponseInterface 16 | */ 17 | public function handle(\Psr\Http\Message\ServerRequestInterface $serverRequest, \FastD\Middleware\DelegateInterface $delegate) 18 | { 19 | $str = 'after' . PHP_EOL; 20 | echo $str; 21 | return $delegate($serverRequest); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/middleware/BeforeMiddleware.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2016 6 | * 7 | * @link https://www.github.com/janhuang 8 | * @link http://www.fast-d.cn/ 9 | */ 10 | class BeforeMiddleware extends \FastD\Middleware\Middleware 11 | { 12 | /** 13 | * @param \Psr\Http\Message\ServerRequestInterface $serverRequest 14 | * @param \FastD\Middleware\DelegateInterface $delegate 15 | * @return \Psr\Http\Message\ResponseInterface 16 | */ 17 | public function handle(\Psr\Http\Message\ServerRequestInterface $serverRequest, \FastD\Middleware\DelegateInterface $delegate) 18 | { 19 | $str = 'before' . PHP_EOL; 20 | echo $str; 21 | return $delegate($serverRequest); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/middleware/BreakerMiddleware.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2016 9 | * 10 | * @link https://www.github.com/janhuang 11 | * @link http://www.fast-d.cn/ 12 | */ 13 | class BreakerMiddleware extends Middleware 14 | { 15 | /** 16 | * @param \Psr\Http\Message\ServerRequestInterface $serverRequest 17 | * @param \FastD\Middleware\DelegateInterface $delegate 18 | * @return \Psr\Http\Message\ResponseInterface 19 | */ 20 | public function handle( 21 | \Psr\Http\Message\ServerRequestInterface $serverRequest, 22 | \FastD\Middleware\DelegateInterface $delegate 23 | ) { 24 | if ('break' == $serverRequest->getAttribute('name')) { 25 | return new Response('break'); 26 | } 27 | 28 | return $delegate($serverRequest)->withHeader('hello', 'world'); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/middleware/DefaultMiddleware.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2016 6 | * 7 | * @link https://www.github.com/janhuang 8 | * @link http://www.fast-d.cn/ 9 | */ 10 | class DefaultMiddleware extends \FastD\Middleware\Middleware 11 | { 12 | /** 13 | * @param \Psr\Http\Message\ServerRequestInterface $serverRequest 14 | * @param \FastD\Middleware\DelegateInterface $delegate 15 | * @return \Psr\Http\Message\ResponseInterface 16 | */ 17 | public function handle(\Psr\Http\Message\ServerRequestInterface $serverRequest, \FastD\Middleware\DelegateInterface $delegate) 18 | { 19 | echo 'default'; 20 | } 21 | } -------------------------------------------------------------------------------- /tests/middleware/GlobalMiddleware.php: -------------------------------------------------------------------------------- 1 |