src
7 years ago
.hhconfig
7 years ago
.travis.yml
7 years ago
FastRoute.hhi
7 years ago
LICENSE
7 years ago
README.md
7 years ago
composer.json
7 years ago
phpunit.xml
7 years ago
psalm.xml
7 years ago
README.md
314 lines
| 1 | FastRoute - Fast request router for PHP |
| 2 | ======================================= |
| 3 | |
| 4 | This library provides a fast implementation of a regular expression based router. [Blog post explaining how the |
| 5 | implementation works and why it is fast.][blog_post] |
| 6 | |
| 7 | Install |
| 8 | ------- |
| 9 | |
| 10 | To install with composer: |
| 11 | |
| 12 | ```sh |
| 13 | composer require nikic/fast-route |
| 14 | ``` |
| 15 | |
| 16 | Requires PHP 5.4 or newer. |
| 17 | |
| 18 | Usage |
| 19 | ----- |
| 20 | |
| 21 | Here's a basic usage example: |
| 22 | |
| 23 | ```php |
| 24 | <?php |
| 25 | |
| 26 | require '/path/to/vendor/autoload.php'; |
| 27 | |
| 28 | $dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { |
| 29 | $r->addRoute('GET', '/users', 'get_all_users_handler'); |
| 30 | // {id} must be a number (\d+) |
| 31 | $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); |
| 32 | // The /{title} suffix is optional |
| 33 | $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); |
| 34 | }); |
| 35 | |
| 36 | // Fetch method and URI from somewhere |
| 37 | $httpMethod = $_SERVER['REQUEST_METHOD']; |
| 38 | $uri = $_SERVER['REQUEST_URI']; |
| 39 | |
| 40 | // Strip query string (?foo=bar) and decode URI |
| 41 | if (false !== $pos = strpos($uri, '?')) { |
| 42 | $uri = substr($uri, 0, $pos); |
| 43 | } |
| 44 | $uri = rawurldecode($uri); |
| 45 | |
| 46 | $routeInfo = $dispatcher->dispatch($httpMethod, $uri); |
| 47 | switch ($routeInfo[0]) { |
| 48 | case FastRoute\Dispatcher::NOT_FOUND: |
| 49 | // ... 404 Not Found |
| 50 | break; |
| 51 | case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: |
| 52 | $allowedMethods = $routeInfo[1]; |
| 53 | // ... 405 Method Not Allowed |
| 54 | break; |
| 55 | case FastRoute\Dispatcher::FOUND: |
| 56 | $handler = $routeInfo[1]; |
| 57 | $vars = $routeInfo[2]; |
| 58 | // ... call $handler with $vars |
| 59 | break; |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | ### Defining routes |
| 64 | |
| 65 | The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts |
| 66 | a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling |
| 67 | `addRoute()` on the collector instance: |
| 68 | |
| 69 | ```php |
| 70 | $r->addRoute($method, $routePattern, $handler); |
| 71 | ``` |
| 72 | |
| 73 | The `$method` is an uppercase HTTP method string for which a certain route should match. It |
| 74 | is possible to specify multiple valid methods using an array: |
| 75 | |
| 76 | ```php |
| 77 | // These two calls |
| 78 | $r->addRoute('GET', '/test', 'handler'); |
| 79 | $r->addRoute('POST', '/test', 'handler'); |
| 80 | // Are equivalent to this one call |
| 81 | $r->addRoute(['GET', 'POST'], '/test', 'handler'); |
| 82 | ``` |
| 83 | |
| 84 | By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` |
| 85 | and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify |
| 86 | a custom pattern by writing `{bar:[0-9]+}`. Some examples: |
| 87 | |
| 88 | ```php |
| 89 | // Matches /user/42, but not /user/xyz |
| 90 | $r->addRoute('GET', '/user/{id:\d+}', 'handler'); |
| 91 | |
| 92 | // Matches /user/foobar, but not /user/foo/bar |
| 93 | $r->addRoute('GET', '/user/{name}', 'handler'); |
| 94 | |
| 95 | // Matches /user/foo/bar as well |
| 96 | $r->addRoute('GET', '/user/{name:.+}', 'handler'); |
| 97 | ``` |
| 98 | |
| 99 | Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` |
| 100 | is not a valid placeholder, because `()` is a capturing group. Instead you can use either |
| 101 | `{lang:en|de}` or `{lang:(?:en|de)}`. |
| 102 | |
| 103 | Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` |
| 104 | will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, |
| 105 | not in the middle of a route. |
| 106 | |
| 107 | ```php |
| 108 | // This route |
| 109 | $r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); |
| 110 | // Is equivalent to these two routes |
| 111 | $r->addRoute('GET', '/user/{id:\d+}', 'handler'); |
| 112 | $r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); |
| 113 | |
| 114 | // Multiple nested optional parts are possible as well |
| 115 | $r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); |
| 116 | |
| 117 | // This route is NOT valid, because optional parts can only occur at the end |
| 118 | $r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); |
| 119 | ``` |
| 120 | |
| 121 | The `$handler` parameter does not necessarily have to be a callback, it could also be a controller |
| 122 | class name or any other kind of data you wish to associate with the route. FastRoute only tells you |
| 123 | which handler corresponds to your URI, how you interpret it is up to you. |
| 124 | |
| 125 | #### Shorcut methods for common request methods |
| 126 | |
| 127 | For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example: |
| 128 | |
| 129 | ```php |
| 130 | $r->get('/get-route', 'get_handler'); |
| 131 | $r->post('/post-route', 'post_handler'); |
| 132 | ``` |
| 133 | |
| 134 | Is equivalent to: |
| 135 | |
| 136 | ```php |
| 137 | $r->addRoute('GET', '/get-route', 'get_handler'); |
| 138 | $r->addRoute('POST', '/post-route', 'post_handler'); |
| 139 | ``` |
| 140 | |
| 141 | #### Route Groups |
| 142 | |
| 143 | Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix. |
| 144 | |
| 145 | For example, defining your routes as: |
| 146 | |
| 147 | ```php |
| 148 | $r->addGroup('/admin', function (RouteCollector $r) { |
| 149 | $r->addRoute('GET', '/do-something', 'handler'); |
| 150 | $r->addRoute('GET', '/do-another-thing', 'handler'); |
| 151 | $r->addRoute('GET', '/do-something-else', 'handler'); |
| 152 | }); |
| 153 | ``` |
| 154 | |
| 155 | Will have the same result as: |
| 156 | |
| 157 | ```php |
| 158 | $r->addRoute('GET', '/admin/do-something', 'handler'); |
| 159 | $r->addRoute('GET', '/admin/do-another-thing', 'handler'); |
| 160 | $r->addRoute('GET', '/admin/do-something-else', 'handler'); |
| 161 | ``` |
| 162 | |
| 163 | Nested groups are also supported, in which case the prefixes of all the nested groups are combined. |
| 164 | |
| 165 | ### Caching |
| 166 | |
| 167 | The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless |
| 168 | caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated |
| 169 | routing data and construct the dispatcher from the cached information: |
| 170 | |
| 171 | ```php |
| 172 | <?php |
| 173 | |
| 174 | $dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) { |
| 175 | $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); |
| 176 | $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); |
| 177 | $r->addRoute('GET', '/user/{name}', 'handler2'); |
| 178 | }, [ |
| 179 | 'cacheFile' => __DIR__ . '/route.cache', /* required */ |
| 180 | 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ |
| 181 | ]); |
| 182 | ``` |
| 183 | |
| 184 | The second parameter to the function is an options array, which can be used to specify the cache |
| 185 | file location, among other things. |
| 186 | |
| 187 | ### Dispatching a URI |
| 188 | |
| 189 | A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method |
| 190 | accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them |
| 191 | appropriately) is your job - this library is not bound to the PHP web SAPIs. |
| 192 | |
| 193 | The `dispatch()` method returns an array whose first element contains a status code. It is one |
| 194 | of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the |
| 195 | method not allowed status the second array element contains a list of HTTP methods allowed for |
| 196 | the supplied URI. For example: |
| 197 | |
| 198 | [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] |
| 199 | |
| 200 | > **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the |
| 201 | `Allow:` header to detail available methods for the requested resource. Applications using FastRoute |
| 202 | should use the second array element to add this header when relaying a 405 response. |
| 203 | |
| 204 | For the found status the second array element is the handler that was associated with the route |
| 205 | and the third array element is a dictionary of placeholder names to their values. For example: |
| 206 | |
| 207 | /* Routing against GET /user/nikic/42 */ |
| 208 | |
| 209 | [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] |
| 210 | |
| 211 | ### Overriding the route parser and dispatcher |
| 212 | |
| 213 | The routing process makes use of three components: A route parser, a data generator and a |
| 214 | dispatcher. The three components adhere to the following interfaces: |
| 215 | |
| 216 | ```php |
| 217 | <?php |
| 218 | |
| 219 | namespace FastRoute; |
| 220 | |
| 221 | interface RouteParser { |
| 222 | public function parse($route); |
| 223 | } |
| 224 | |
| 225 | interface DataGenerator { |
| 226 | public function addRoute($httpMethod, $routeData, $handler); |
| 227 | public function getData(); |
| 228 | } |
| 229 | |
| 230 | interface Dispatcher { |
| 231 | const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2; |
| 232 | |
| 233 | public function dispatch($httpMethod, $uri); |
| 234 | } |
| 235 | ``` |
| 236 | |
| 237 | The route parser takes a route pattern string and converts it into an array of route infos, where |
| 238 | each route info is again an array of it's parts. The structure is best understood using an example: |
| 239 | |
| 240 | /* The route /user/{id:\d+}[/{name}] converts to the following array: */ |
| 241 | [ |
| 242 | [ |
| 243 | '/user/', |
| 244 | ['id', '\d+'], |
| 245 | ], |
| 246 | [ |
| 247 | '/user/', |
| 248 | ['id', '\d+'], |
| 249 | '/', |
| 250 | ['name', '[^/]+'], |
| 251 | ], |
| 252 | ] |
| 253 | |
| 254 | This array can then be passed to the `addRoute()` method of a data generator. After all routes have |
| 255 | been added the `getData()` of the generator is invoked, which returns all the routing data required |
| 256 | by the dispatcher. The format of this data is not further specified - it is tightly coupled to |
| 257 | the corresponding dispatcher. |
| 258 | |
| 259 | The dispatcher accepts the routing data via a constructor and provides a `dispatch()` method, which |
| 260 | you're already familiar with. |
| 261 | |
| 262 | The route parser can be overwritten individually (to make use of some different pattern syntax), |
| 263 | however the data generator and dispatcher should always be changed as a pair, as the output from |
| 264 | the former is tightly coupled to the input of the latter. The reason the generator and the |
| 265 | dispatcher are separate is that only the latter is needed when using caching (as the output of |
| 266 | the former is what is being cached.) |
| 267 | |
| 268 | When using the `simpleDispatcher` / `cachedDispatcher` functions from above the override happens |
| 269 | through the options array: |
| 270 | |
| 271 | ```php |
| 272 | <?php |
| 273 | |
| 274 | $dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { |
| 275 | /* ... */ |
| 276 | }, [ |
| 277 | 'routeParser' => 'FastRoute\\RouteParser\\Std', |
| 278 | 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', |
| 279 | 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', |
| 280 | ]); |
| 281 | ``` |
| 282 | |
| 283 | The above options array corresponds to the defaults. By replacing `GroupCountBased` by |
| 284 | `GroupPosBased` you could switch to a different dispatching strategy. |
| 285 | |
| 286 | ### A Note on HEAD Requests |
| 287 | |
| 288 | The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: |
| 289 | |
| 290 | > The methods GET and HEAD MUST be supported by all general-purpose servers |
| 291 | |
| 292 | To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an |
| 293 | available GET route for a given resource. The PHP web SAPI transparently removes the entity body |
| 294 | from HEAD responses so this behavior has no effect on the vast majority of users. |
| 295 | |
| 296 | However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST |
| 297 | NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is |
| 298 | *your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. |
| 299 | |
| 300 | Finally, note that applications MAY always specify their own HEAD method route for a given |
| 301 | resource to bypass this behavior entirely. |
| 302 | |
| 303 | ### Credits |
| 304 | |
| 305 | This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. |
| 306 | |
| 307 | A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. |
| 308 | |
| 309 | |
| 310 | [2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" |
| 311 | [blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html |
| 312 | [levi]: https://github.com/morrisonlevi |
| 313 | [rdlowrey]: https://github.com/rdlowrey |
| 314 |