Code Monkey home page Code Monkey logo

fastroute's Introduction

FastRoute - Fast request router for PHP

Build Status

This library provides a fast implementation of a regular expression based router. Blog post explaining how the implementation works and why it is fast.

Install

To install with composer:

composer require nikic/fast-route

Requires PHP 8.1 or newer.

Usage

Here's a basic usage example:

<?php

require '/path/to/vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\ConfigureRoutes $r) {
    $r->addRoute('GET', '/users', 'get_all_users_handler');
    // {id} must be a number (\d+)
    $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler');
    // The /{title} suffix is optional
    $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler');
});

// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // ... call $handler with $vars
        break;
}

Defining routes

The routes are defined by calling the FastRoute\simpleDispatcher() function, which accepts a callable taking a FastRoute\ConfigureRoutes instance. The routes are added by calling addRoute() on the collector instance:

$r->addRoute($method, $routePattern, $handler);

The $method is an uppercase HTTP method string for which a certain route should match. It is possible to specify multiple valid methods using an array:

// These two calls
$r->addRoute('GET', '/test', 'handler');
$r->addRoute('POST', '/test', 'handler');
// Are equivalent to this one call
$r->addRoute(['GET', 'POST'], '/test', 'handler');

By default, the $routePattern uses a syntax where {foo} specifies a placeholder with name foo and matching the regex [^/]+. To adjust the pattern the placeholder matches, you can specify a custom pattern by writing {bar:[0-9]+}. Some examples:

// Matches /user/42, but not /user/xyz
$r->addRoute('GET', '/user/{id:\d+}', 'handler');

// Matches /user/foobar, but not /user/foo/bar
$r->addRoute('GET', '/user/{name}', 'handler');

// Matches /user/foo/bar as well
$r->addRoute('GET', '/user/{name:.+}', 'handler');

Custom patterns for route placeholders cannot use capturing groups. For example {lang:(en|de)} is not a valid placeholder, because () is a capturing group. Instead you can use either {lang:en|de} or {lang:(?:en|de)}.

Furthermore, parts of the route enclosed in [...] are considered optional, so that /foo[bar] will match both /foo and /foobar. Optional parts are only supported in a trailing position, not in the middle of a route.

// This route
$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler');
// Is equivalent to these two routes
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler');

// Multiple nested optional parts are possible as well
$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler');

// This route is NOT valid, because optional parts can only occur at the end
$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler');

The $handler parameter does not necessarily have to be a callback, it could also be a controller class name or any other kind of data you wish to associate with the route. FastRoute only tells you which handler corresponds to your URI, how you interpret it is up to you.

Shortcut methods for common request methods

For the GET, POST, PUT, PATCH, DELETE and HEAD request methods shortcut methods are available. For example:

$r->get('/get-route', 'get_handler');
$r->post('/post-route', 'post_handler');

Is equivalent to:

$r->addRoute('GET', '/get-route', 'get_handler');
$r->addRoute('POST', '/post-route', 'post_handler');

Route Groups

Additionally, you can specify routes inside a group. All routes defined inside a group will have a common prefix.

For example, defining your routes as:

$r->addGroup('/admin', function (FastRoute\ConfigureRoutes $r) {
    $r->addRoute('GET', '/do-something', 'handler');
    $r->addRoute('GET', '/do-another-thing', 'handler');
    $r->addRoute('GET', '/do-something-else', 'handler');
});

Will have the same result as:

$r->addRoute('GET', '/admin/do-something', 'handler');
$r->addRoute('GET', '/admin/do-another-thing', 'handler');
$r->addRoute('GET', '/admin/do-something-else', 'handler');

Nested groups are also supported, in which case the prefixes of all the nested groups are combined.

Caching

The reason simpleDispatcher accepts a callback for defining the routes is to allow seamless caching. By using cachedDispatcher instead of simpleDispatcher you can cache the generated routing data and construct the dispatcher from the cached information:

<?php

$dispatcher = FastRoute\cachedDispatcher(function(FastRoute\ConfigureRoutes $r) {
    $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
    $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
    $r->addRoute('GET', '/user/{name}', 'handler2');
}, [
    'cacheKey' => __DIR__ . '/route.cache', /* required */
    // 'cacheFile' => __DIR__ . '/route.cache', /* will still work for v1 compatibility */
    'cacheDisabled' => IS_DEBUG_ENABLED,     /* optional, enabled by default */
    'cacheDriver' => FastRoute\Cache\FileCache::class, /* optional, class name or instance of the cache driver - defaults to file cache */
]);

The second parameter to the function is an options array, which can be used to specify the cache key (e.g. file location when using files for caching), caching driver, among other things.

Dispatching a URI

A URI is dispatched by calling the dispatch() method of the created dispatcher. This method accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them appropriately) is your job - this library is not bound to the PHP web SAPIs.

The dispatch() method returns an array whose first element contains a status code. It is one of Dispatcher::NOT_FOUND, Dispatcher::METHOD_NOT_ALLOWED and Dispatcher::FOUND. For the method not allowed status the second array element contains a list of HTTP methods allowed for the supplied URI. For example:

[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]

NOTE: The HTTP specification requires that a 405 Method Not Allowed response include the Allow: header to detail available methods for the requested resource. Applications using FastRoute should use the second array element to add this header when relaying a 405 response.

For the found status the second array element is the handler that was associated with the route and the third array element is a dictionary of placeholder names to their values. For example:

/* Routing against GET /user/nikic/42 */

[FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']]

Overriding the route parser and dispatcher

The routing process makes use of three components: A route parser, a data generator and a dispatcher. The three components adhere to the following interfaces:

<?php

namespace FastRoute;

interface RouteParser {
    public function parse($route);
}

interface DataGenerator {
    public function addRoute($httpMethod, $routeData, $handler);
    public function getData();
}

interface Dispatcher {
    const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2;

    public function dispatch($httpMethod, $uri);
}

The route parser takes a route pattern string and converts it into an array of route infos, where each route info is again an array of its parts. The structure is best understood using an example:

/* The route /user/{id:\d+}[/{name}] converts to the following array: */
[
    [
        '/user/',
        ['id', '\d+'],
    ],
    [
        '/user/',
        ['id', '\d+'],
        '/',
        ['name', '[^/]+'],
    ],
]

This array can then be passed to the addRoute() method of a data generator. After all routes have been added the getData() of the generator is invoked, which returns all the routing data required by the dispatcher. The format of this data is not further specified - it is tightly coupled to the corresponding dispatcher.

The dispatcher accepts the routing data via a constructor and provides a dispatch() method, which you're already familiar with.

The route parser can be overwritten individually (to make use of some different pattern syntax), however the data generator and dispatcher should always be changed as a pair, as the output from the former is tightly coupled to the input of the latter. The reason the generator and the dispatcher are separate is that only the latter is needed when using caching (as the output of the former is what is being cached.)

When using the simpleDispatcher / cachedDispatcher functions from above the override happens through the options array:

<?php

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\ConfigureRoutes $r) {
    /* ... */
}, [
    'routeParser' => 'FastRoute\\RouteParser\\Std',
    'dataGenerator' => 'FastRoute\\DataGenerator\\MarkBased',
    'dispatcher' => 'FastRoute\\Dispatcher\\MarkBased',
]);

The above options array corresponds to the defaults. By replacing MarkBased with GroupCountBased you could switch to a different dispatching strategy.

A Note on HEAD Requests

The HTTP spec requires servers to support both GET and HEAD methods:

The methods GET and HEAD MUST be supported by all general-purpose servers

To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an available GET route for a given resource. The PHP web SAPI transparently removes the entity body from HEAD responses so this behavior has no effect on the vast majority of users.

However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is your responsibility; FastRoute has no purview to prevent you from breaking HTTP in such cases.

Finally, note that applications MAY always specify their own HEAD method route for a given resource to bypass this behavior entirely.

Credits

This library is based on a router that Levi Morrison implemented for the Aerys server.

A large number of tests, as well as HTTP compliance considerations, were provided by Daniel Lowrey.

fastroute's People

Contributors

acidvertigo avatar allineer avatar anlutro avatar byjg avatar carusogabriel avatar codemasher avatar dfdxalex avatar fredemmott avatar kronthto avatar lcobucci avatar localheinz avatar mruz avatar nikic avatar ninoskopac avatar nyholm avatar ragboyjr avatar rdlowrey avatar ronniskansing avatar royopa avatar rquadling avatar samnela avatar siebelstim avatar signpostmarv avatar sstruk avatar sy-records avatar szepeviktor avatar tomwright avatar tyler-sommer avatar xpaw avatar yyinsomnia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastroute's Issues

Optional and Optional-Nested Matches

This works:
/hi/{name}

But it seems:
/hi/{first}(/{last})
/hi/{first}(/{middle}(/{last}))

Do not -- any plan to?

This weekend when I tried to migrate away from slim, I didn't realize how much I was depending on this capability. I don't think the syntax needs to match (although that would be neat) but the functionality would be great.

I've experimented a bit with it, but haven't yet figured out how to add it.

Handling "ends with" pattern matching.

I posted this on StackOverflow, without much response: http://stackoverflow.com/questions/32281307/handling-ends-with-routes-in-slim-3-beta

I'm trying to figure out how to do an 'ends with' regex with this router. The route I have doesn't appear to be working:

$app->get('/{uri:.+(?:css|js|gif|jpg|jpeg|png)$}', function (){ [...] });

I've tried others:

$app->get('/{uri:.+\.(?:css|js|gif|jpg|jpeg|png)$}', function (){ [...] });
$app->get('/{uri:.+(?:css|js|gif|jpg|jpeg|png)}', function (){ [...] });

Does FastRoute support suffix anchoring in the regex?

Comparing ID and Names on MySql

How to compare this ID and Names from mysql with

$Poล‚ฤ…cz = new \Projekt\PDO\Poล‚ฤ…czenieBazaDanych();
#$a = $Poล‚ฤ…cz->Zapytaj('SELECT * FROM `uลผytkownicy`');
$ages = $Poล‚ฤ…cz->Rekord("SELECT * FROM `uลผytkownicy` WHERE  `id` = :id", array("id"=>"1"));


$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/' .rawurlencode('uลผytkownik') . $ages['ID']. rawurlencode('krฤ…ลผ'), 'uchwyt-1');
    // Or alternatively
    #$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'common_handler');
});

So I have no access to this variables $ages['ID'] under $dispatcher.

A question: dataGenerators take time

Hi @nikic,

1st of all (if i forgot before) thanks for sharing this idea.

The route parser is really super-fast. But i believe that this idea SHOULD always be implemented using a caching mechanism as per your examples if we mostly use routes with parameters

Without a caching mechanism basically the work that traditional routers do for each route pattern is just migrated to the dataGenerator. A fast-route data generator uses regex functions to build the pattern chunks working on the routes when added (i.e. one by one).

i did a simple test:

ini_set('display_errors', true);

require __DIR__ . '/../vendor/autoload.php';

$scriptName = $_SERVER['SCRIPT_NAME'];
$basePath   = dirname($scriptName);
$httpMethod = $_SERVER['REQUEST_METHOD'];
$requestUri = $_SERVER['REQUEST_URI'];
$pathInfo   = substr($requestUri, strlen($basePath));

$t0 = microtime(true);

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    for ($i = 0; $i < 1000; $i++) {
        $n = sprintf('%03s', $i);
        $r->addRoute('GET', '/test/route-' . $n . '/{param1}/{param2}', 'test-handler-' . $n);
    }
});
$t1 = microtime(true);

$routeInfo = $dispatcher->dispatch($httpMethod, $pathInfo);
$t2 = microtime(true);

echo '<pre>';
print_r($routeInfo);
echo "gt = " . 1000 * round($t1 - $t0, 6) . " ms\n";
echo "mt = " . 1000 * round($t2 - $t1, 6) . " ms\n";

worst case scenario
/test/route-999/abc/def

generator time gt ~ 36 ms
matching time mt ~ 0.36 ms

the sum is similar of what i get with a traditional router.

Did I miss something? Or can You confirm that is quite essential to use a cacheDispatcher?

kind regards

A non-matching METHOD is 'METHOD_NOT_ALLOWED' on static routes

In this example performing the request 'GET /users.json' the static route /users.json matches the request and returns METHOD_NOT_ALLOWED. Shouldn't the GET /{entity}.json route match the request?

<?php

require '/path/to/FastRoute/src/bootstrap.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('POST', '/user.json', 'postHandler');
    $r->addRoute('GET', '/{entity}.json', 'getHandler');
});

$routeInfo = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        print_r($routeInfo);
        echo "METHOD NOT ALLOWED";
        die(405);
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // ... call $handler with $vars
        break;
}
exit;

Bad error code in BadRouteException is 0

Hello,

While debugging my code I found that the BadRouteException thrown when you have a bad route has the error code of 0. I think it should be 500 (internal server error).

dd($e); shows:

BadRouteException {#139
#message: "Static route "/api/v1/test/top" is shadowed by previously defined variable route "/api/v1/test/([^/]+)" for method "GET""
#code: 0
#file: "/home/vagrant/Code/lumen/prject/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php"
#line: 64
......

Arbitrary Number of Additional Parameters

Is there any way to do an arbitrary number of additional route parameters? For example I would like to define a route such as:

/{controller}/{action}/{params}

where params would be a zero-indexed array of any additional parameters (instead of naming each one)?

how to execute test demo

I use the command "vendor/bin/phpunit DispatcherTest.php testFoundDispatches",but is fail

PHPUnit 3.7.21 by Sebastian Bergmann.

FFFFFFF

Time: 0 seconds, Memory: 2.50Mb

There were 7 failures:

  1. Warning
    Cannot instantiate class "FastRoute\Dispatcher\DispatcherTest".

  2. Warning
    Cannot instantiate class "FastRoute\Dispatcher\DispatcherTest".

  3. Warning
    Cannot instantiate class "FastRoute\Dispatcher\DispatcherTest".

  4. Warning
    Cannot instantiate class "FastRoute\Dispatcher\DispatcherTest".

  5. Warning
    Cannot instantiate class "FastRoute\Dispatcher\DispatcherTest".

Redirect path

Hi friends,

How can I redirect the URL?
For example:
If the user goes to \signin
I want to redirect to \login

Thanks

Laravel

Can I override laravels route provider with this? If so, how would I go about using Fast route instead?

No issue...just a Question

Hello @nikic ,
I started lookign at your router implementation and parsing techniques a while ago. Thank You for sharing. I use a small framework now loosely based on slim 2.4 + addons like forms, db, orm, modules.
The router/route are a modified version of slim router version 2.4
I modified it in order to manage routes like the following:

/member(/index(-:page(/:sort)))
ex
/member => member list 1st page
/member/index =>member list 1st page
/member/index-3 =>member list page 3
/member/index-5/name.asc => member list page 5 results ordered by name ASC (name is translated into a column db sorting)

can this ( nested optional parameters with optional parts not belonging to route parameter value as the hypen before the page number in my examples) be achieved with your router?

oh, i forgot....the router supports route priority.

kind regards

Named routes

Would it be worthwhile to extend the implementation to supports named routes? I don't think the routing algorithm cares about names, but it would be helpful for creating url generation helpers and the like.

Is FastRoute where that should be implemented, anyway?

POST route + custom placeholder pattern is being overridden by GET route + default placeholder pattern

In the code below, the GET route is overriding the POST route whenever I use a custom pattern. However, if I try the same POST route with the default pattern, it does correctly match.

<?php

require_once './vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET',  '/user/{name}', 'GET with default placeholder pattern');
    $r->addRoute('POST', '/user/{name:[a-z]+}', 'POST with custom placeholder pattern');

    // This route correctly matches if uncommented
    // $r->addRoute('POST', '/user/{name}', 'POST with default placeholder pattern');
});

function handle($routeInfo) {
    print_r($routeInfo);
    echo "\n";
}

handle($dispatcher->dispatch('GET', '/user/bob'));
handle($dispatcher->dispatch('POST', '/user/fred')); // does not work

Support for adding routes dynamically

Hello,

The lib lacks support for adding rรงoutes dynamically. With Express for example, we can add a route at any moment by doing app.get('/my/route', callback). Reading the source code, I see that FastRoute isn't written in a way that let us easily add routes after the initial "mass import" and "mass parsing". So, if I want to add routes when the app is running, I have to recreate from zero a new dispatcher and destruct the old.

I know that this library isn't you main hobby, but do you think you could, in the future, add support for adding routes in an expressy-way?

Anyway, thanks for your work on this routing library, that's exactly what I needed (simple and fast).

In Chrome strange bug.

I found today in my case strange bug. This is my code:

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/projekt/Framework/' . rawurlencode('uลผytkownik') . '/{id:\d+}/{name}', 'uchwyt-1');
});

// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);

switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        echo 'Not Found!';
        // ... 404 Not Found
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        break;
    case FastRoute\Dispatcher::FOUND:
        echo 'Found!';
        // ... call $handler with $vars
        break;
}

All works fine but when I go to this page: http://localhost/projekt/Framework/u%C5%BCytkownik/12/hhh Chrome display "Not Found!" but this same address in Waterfox 64 working good. If I delete one h from end of this link Chrome display "Found!". So this triple h give an error in Google Chrome.

5.3+ support

I'd really like to use this library but we're stuck with PHP 5.3.10 for the time being (servers are all Ubuntu LTS 12.04) and FastRoute requires PHP 5.4+.

I'd be happy to make the code 5.3+ compatible but I'm guessing you made the 5.4+ decision for a good reason?

Less information

This script has less information about using it. Where is info about .htaccess? Why in basic usage is:
"$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler2');" this is not working when anyone using subfolder as root of script. because router don't expecting subfolder/user/12/mario.

Discussion RouteInfo class

Hello @nikic ,

what about implementing a separate simple RouteInfo class also implementing \ArrayAccess to be backward compatible?

I believe that calling class methods would result in a more semantic code, when handling dispatcher results

smt like:

class RouteInfo implements \ArrayAccess
{
    private $result;
    private $handler;
    private $vars;
    private $alloweMethods;
    public function __construct($result, array $properties = []) 
    {
        $this->result = (int) $result;
        foreach ($properties as $name => $value) {
            $this->{$name} = $value;
        }
    }

    public function getResult() {...}
    public function getHandler() {...}
    public function getVariables() {...}
    public function getAllowedMethods() {...}

    public function found() {
        return $this->result === Dispatcher::FOUND;
    }

    public function notFound() {
        return $this->result === Dispatcher::NOT_FOUND;
    }

    public function methodNotAllowed() {
        return $this->result === Dispatcher::METHOD_NOT_ALLOWED;
    }

    public function offsetGet($offset) 
    {
        if (0 === $offset) {
            return $this->result;
        }
        if (1 === $offset) {
            if ($this->result === Dispatcher::FOUND) return $this->handler;
            if ($this->result === Dispatcher::METHOD_NOT_ALLOWED) return $this->allowedMethods;
            return null;
        }
        if (2 === $offset) {
            if ($this->result === Dispatcher::FOUND) return $this->vars;
            return null;
        }
        return null;
    }

    //...implements other ArrayAccess methods
}

maybe also moving result parsing constant inside this class....

Basic usage of fastroute

hi,

I have tried many ways but couldn't get it work, my code is as below

  require_once("./vendor/autoload.php");

  $dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $route) {
    $route->addRoute('GET', '/', 'Router::getIndex');
  });

if I change Router::getIndex from string to variable, it will always be called. I also tried change Router to some class doesn't exists, no errors.

Add ability to inject RouteCollector into simpleDispatcher

I extended RouteCollector to do some pre-processing on the route definitions before adding them and when trying to use simpleDispatcher, I discovered that I needed to duplicate that code because FastRoute\RouteCollector is a hard-wired dependency.

What do you think about adding "routeCollector" => "FastRoute\\RouteCollector" to the $options array in simpleDispatcher? It would be a backwards compatible change but add the ability to inject a custom RouteCollector.

How to use $_PUT and $_DELETE?

I tried $_PUT and $_DELETE but the error comes up that says no $_PUT variable found. Bu it should be the basic PHP right?

['PUT ', '/', ['Example\Controllers\HomeController', 'doUpdate']],

Thanks.

Bogus "Number of opening '[' and closing ']' does not match" error

This route yields "Number of opening '[' and closing ']' does not match":

/forms/{action:listing|audit}[/{id}]/

This one doesn't:

/forms/{action:listing|audit}/[{id}/]

I'm unsure if there's a good reason for this, or it's just a bug. Either way the error message is misleading.

Escaping {}

POST    /product/123                Update product.
GET     /product                    Get collection of all products.
GET     /product/[nid=foo]          Get product where nid is "foo".
GET     /product/{nid=foo}          Get collection of products where nid is "foo".

I am using the above naming convention, which requires being able to use {} as part of the resource identifier. I have tried escaping using a backslash, but that did not work:

$r->addRoute('GET', 'product/\{{name}={value}\}', 'getProducts');

Simple test case:

$this->dispatcher = \FastRoute\simpleDispatcher(function(\FastRoute\RouteCollector $r) {
    $r->addRoute('GET', 'XXX/\{YYY\}', 'ZZZ');
});

var_dump( $this->dispatcher->dispatch('GET', 'XXX/\{YYY\}') );

Will resolve to the resource:

array(3) {
  [0]=>
  int(1)
  [1]=>
  string(3) "ZZZ"
  [2]=>
  array(0) {
  }
}

While:

var_dump( $this->dispatcher->dispatch('GET', 'XXX/{YYY}') );

Will not.

Quick (temporary) fix is to add a leading slash before {} in the request URI.

Adding a .hhi file for typeinformation

Hi, the hhvm-documentation uses your FastRoute project. ๐Ÿ‘

However, this prevents one file to be in strict mode. I am planning on resolving this with a .hhi file.
How do you feel about merging that file into this repository? I'd be useful for more hack projects using the router and would not interfere with php projects.

I'm interested in your opinion on that before I'm writing the hhi for that :)

Multiple url for single controller

Hi,

I would like to use the same controller for multiple urls (because I show exactly the same data, but just in different languages) and I've tried this code:

    $r->addRoute('GET', '/{lang:(it|ch)}/',       'Home');
    $r->addRoute('GET', '/{lang:(it|ch)}/blog/',  'Blog');
    $r->addRoute('GET', '/sitemap/',       'Sitemap');

but this does not work. I get this error:

Notice: Undefined offset: 4 in /data/www/www.mysite.lan/lib/FastRoute/Dispatcher/GroupCountBased.php on line 16

Call Stack:
    0.0004     273928   1. {main}() /data/www/www.mysite.lan/index.php:0
    0.0257    1815680   2. FastRoute\Dispatcher\RegexBasedAbstract-&gt;dispatch() /data/www/www.mysite.lan/index.php:46
    0.0257    1815832   3. FastRoute\Dispatcher\GroupCountBased-&gt;dispatchVariableRoute() /data/www/www.mysite.lan/lib/FastRoute/Dispatcher/RegexBasedAbstract.php:20

Warning: Invalid argument supplied for foreach() in /data/www/www.mysite.lan/lib/FastRoute/Dispatcher/GroupCountBased.php on line 20

Call Stack:
    0.0004     273928   1. {main}() /data/www/www.mysite.lan/index.php:0
    0.0257    1815680   2. FastRoute\Dispatcher\RegexBasedAbstract-&gt;dispatch() /data/www/www.mysite.lan/index.php:46
    0.0257    1815832   3. FastRoute\Dispatcher\GroupCountBased-&gt;dispatchVariableRoute() /data/www/www.mysite.lan/lib/FastRoute/Dispatcher/RegexBasedAbstract.php:20

and with

$r = $d->dispatch(Core\Site::getRequestMethod(), Core\Site::getRequestURL());

$r[0] is empty, so the application does not know what is the controller to use.

I've tried to dump, in GroupCountBased, the route data inside the foreach:

        foreach ($routeData as $data) {
            var_dump($data);
            if (!preg_match($data['regex'], $uri, $matches)) {
                continue;
            }

just to try to understand the problem, and I've got:

array(2) {
  'regex' =>
  string(38) "~^(?|/((it|ch))/|/((it|ch))/blog/())$~"
  [...]

so I think that the problem is related to the fact that routes is "collected" and the use of | (pipe) between multiple urls broke the original regexs.

I've workarounded it redefining urls:

    $r->addRoute('GET', '/it/',       'HomeIT');
    $r->addRoute('GET', '/ch/',       'HomeCH');
    $r->addRoute('GET', '/it/blog/',  'BlogIT');
    $r->addRoute('GET', '/ch/blog/',  'BlogCH');

but this does not satisfy me. There is a way to achieve the use of variable url parts for the same controller and at the same time to obtain from the dispatcher the first part of url (it or ch) as a variable?

Consider that I can't simply do

    $r->addRoute('GET', '/{lang:.*}/',       'Home');

because this will also route the /sitemap/ url (and also any other url) and I want to avoid to serve everything with the same controller.

Thank you for reading,
Marco

Questions about MARK based dispatcher

I notice that the MarkBased dispatcher is currently the most performant in your benchmark, and I'm curious to learn a bit more about how it works, as the details stumped me. So, a few questions for my learning:

  1. Why does it it need PHP 5.6?
  2. What is the (*MARK: ...) construct added to the regexes, and how does it work to yield a 'MARK' array key here?
  3. Why is it incrementing a string here rather than just using an integer?

Many thanks!

PHPUnit doesn't run

On a fresh clone of the project, the unit tests don't seem to run.

PHP Fatal error:  Cannot redeclare FastRoute\simpleDispatcher() (previously declared in /home/danny/src/FastRoute/src/functions.php:11) in /home/danny/src/FastRoute/src/functions.php on line 24
PHP Stack trace:
PHP   1. {main}() /home/danny/src/FastRoute/vendor/phpunit/phpunit/phpunit:0
PHP   2. PHPUnit_TextUI_Command::main() /home/danny/src/FastRoute/vendor/phpunit/phpunit/phpunit:56
PHP   3. PHPUnit_TextUI_Command->run() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:138
PHP   4. PHPUnit_TextUI_Command->handleArguments() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:148
PHP   5. PHPUnit_TextUI_Command->handleBootstrap() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:650
PHP   6. PHPUnit_Util_Fileloader::checkAndLoad() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:814
PHP   7. PHPUnit_Util_Fileloader::load() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/Util/Fileloader.php:77
PHP   8. include_once() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/Util/Fileloader.php:93

Fatal error: Cannot redeclare FastRoute\simpleDispatcher() (previously declared in /home/danny/src/FastRoute/src/functions.php:11) in /home/danny/src/FastRoute/src/functions.php on line 24

Call Stack:
    0.0001     225904   1. {main}() /home/danny/src/FastRoute/vendor/phpunit/phpunit/phpunit:0
    0.0017     563944   2. PHPUnit_TextUI_Command::main() /home/danny/src/FastRoute/vendor/phpunit/phpunit/phpunit:56
    0.0017     564568   3. PHPUnit_TextUI_Command->run() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:138
    0.0017     567152   4. PHPUnit_TextUI_Command->handleArguments() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:148
    0.0033     938304   5. PHPUnit_TextUI_Command->handleBootstrap() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:650
    0.0034     946864   6. PHPUnit_Util_Fileloader::checkAndLoad() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/TextUI/Command.php:814
    0.0034     947032   7. PHPUnit_Util_Fileloader::load() /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/Util/Fileloader.php:77
    0.0034     952616   8. include_once('/home/danny/src/FastRoute/test/bootstrap.php') /home/danny/src/FastRoute/vendor/phpunit/phpunit/src/Util/Fileloader.php:93

Multiple Http Methods for a single route

Hi,

I was wondering if it is in the projects scope about having the option to pass multiple methods to a route?

Example?

$route->addRoute(['GET','POST'], '/user/{name}/{id:[0-9]+}', 'handler0');

This is particularly handy if I have an edit/entry form which posts to itself and I will only every have one route to manage for both?

I can do so a submit a pull request if you are interested in the addition?

Is it OK that example does not work?

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/user/{id:\d+}', 'handler1');
    $r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler2');
    // Or alternatively
    $r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'common_handler');
});

Fatal error: Uncaught FastRoute\BadRouteException: Cannot register two routes matching "/user/(\d+)" for method "GET"

Optional Items in RouteCollector

Hello @nikic
Thank you for the awesome Router!
I was wondering if there is a way to have slightly 'cleaner' routes, by using optional elements in the addRoute call?
Example:
Right now I have
$r->addRoute('GET', '/users/{id:[0-9]+}', 'myfunction'); // slash-less
$r->addRoute('GET', '/users/{id:[0-9]+}/', 'myfunction'); // with a slash!

is there a way to combine these with something like
$r->addRoute('GET', '/users/{id:[0-9]+}/?', 'myfunction'); // "?" indicates optional prev token "/"
or
$r->addRoute('GET', '/users/{id:[0-9]+}/{'commentid:[0-9]+'}?', 'myfunction'); // ? indicates optional prev token " {'commentid:[0-9]+'} "

thanks!

Convert functions to static class methods.

Some applications use the router conditionally (say, depending on whether there's cache hit or miss before the router kicks in), and loading functions.php is always loaded which represents a slight performance loss (as these functions will never be called).

The code to load those conditionally means bypassing Composer's autoloading functionality and doing a require_once of functions.php manually, before using them.

If we convert those functions to static methods, their functionality will be identical, but they'll be able to be lazily loaded as any other class.

If I provide a patch for this, would you accept?

cachedDispatcher is trying to add __set_state on all objects

I am having an issue when using the cachedDispatcher, it seems that on var_export it adds a __set_state on all methods and Closures, since by default that method does not exist on my Closure, I get an error when the router tries to re-import this.

Anyone else having this issue or know of how to work around it?

Can't match quotes in url

Trying to use this URL:

/recipes/search?query="my%20query%20string"

Which doesn't match the route set up for the page, like

/recipes/search

All other combinations of characters seem to be ok, apart from quotes.

Multiple optional parameters

Thanks for implementing the support of optional parameters.

How does multiple optional work? e.g. /news[/{year}][/{month}] gives:

Fatal error: Uncaught exception 'FastRoute\BadRouteException' with message 'Number of opening '['
and closing ']' does not match' in 
/www/dev/slim3/develop-testbed/vendor/nikic/fast-route/src/RouteParser/Std.php on line 31

Am I using the wrong syntax?

On accessing $_GET

Hello.

Here's a basic Router I have defined:

$r->addRoute('GET', '/', 'Home');

Thing Is, I need to be able to pass query parameters too (optionally), e.g `?something=1&that=2'.

I was hoping I could just pass them like that and access them using $_GET, but I get a FastRoute\Dispatcher::NOT_FOUND instead.

Help ?:)

Route to URL generation / reverse routing

Previously, I used FastRoute\RouteParser\Std::VARIABLE_REGEX with preg_replace_callback in order to make a FastRoute-compatible route URL generator, but in 0.6 (more specifically 31fa869) this was changed.

Obviously my tests picked it up and I fixed it by wrapping the regex string in ~ and ~x, but I'm now uncertain if that's the correct approach - I guess it won't work properly with the new trailing optional segments.

Could a preg_replace_callback compatible regex, including necessary flags, be part of the public API for FastRoute? Or, taking it a step further, could a "FastRoute-pattern to URL generator" class/function be part of the package?

RouteCollector: private $routeParser;

It's really unhelpful to make a property private. I'm trying to extend the collector to add functionality that allows me to read routes from a file, and I need access to the parser to do a little parameter replacement before the route is added.

This private property means I am having to store the parser twice inside the object. Not really good.

I know it ensures you can maintain the API expectations of the package, but there should be no such thing as a private property, as they are extremely unhelpful.

Is there no scope to make these class properties protected instead?

Autoload performance problems

Hi,
As I see, you have used PHP auto loading in the project. That's cool in terms of code beauty but it is a huge performance drawback. Specially, the excessive use of numerous files, each for a small class. IO is a bottleneck in applications and loading several small files is usually heavier than loading one big file. And worse than that, auto loaded files can not be cached by PHP OP Code cache tools like eAccelerator or APC since they are fully runtime.
I suggest providing a single file, compiled version of your library. Or let auto loading be optional like HTMLPurifier. I mean, we choose FASTroute for being fast, let's not spoil the amount of work you did to optimize the stuff, by a huge start up time.
I think considering library loading time, you might lose the benchmark to Pux!
Anyway, thanks for your great work...

Tag/release

I'd like to use this via composer in a library, but minimum-stability: stable settings are messing it up as this repository has no tags/releases and thus no stable versions on packagist.

I know it can be a slight hassle to keep tags up to date, but even a simple 0.1.0 for the current master branch would help in my case :)

Get parameters and url

Hi :)
How can i allow get parameters for url

for slash ulr (default)
/

not work with GET parameter:

/?param1

Thx for help

Feature Request: More Caching Mechanisms

Hey Nikita,

I am wondering if it would be reasonable to support more caching mechanisms other than the file-based provided? I don't like hitting the filesystem for caches, so that's why I'd prefer something else.

If you agree, I could add a new option (cache or something alike) to cachedDispatcher which would expect a Doctrine\Common\Cache\Cache instance. This way, it would be really easy to support nearly any type of cache. And maybe-maybe introducing a breaking change to remove the existing cacheDisabled and cacheFile options would also be beneficial so users wouldn't be confused about which option to use.

Even though routing isn't a really big concern as you also mentioned in your blog post, I think bigger projects with lots of routes could yet benefit from being able to cache in memory: I only have cc. 25 endoints but my cache file is 7 KB, so it can grow really big.

Do you think this functionality is really worth the "effort"?

UPDATE: After having read this post I am no more convinced about speed improvements. :( However, if you'd like more interopable caching, this feature could be still good.

Conditional error handlers

Currently there are only two errors: method not allowed and not found.

I understand that FastRoute doesn't want to get out of the scope of simple routing, but implementing a system for separate handling of errors depending on the route is quite a mess as you have to write your own route parser.

Say I build a page with 2-3 different parts like public part, admin panel and api (I understand this should be independent, but please take this as an example). So we should have 3 separate 404 pages - one for the public part of the website, one for admin panel and one for an api (in xml/json format).

What I suggest is to have at least some information returned with the error. For example, return the handler of the closest match, stripping parts of the original url one by one.
/admin/users/123/profile -> /admin/users/123 -> /admin/users -> /admin.

I know this is a shitty solution, so I would like to leave this for elaboration by others, maybe some better option to handle this as with the current situtation we should route manually on error.

Route cache is never invalidated

When I create a cachedDispatcher, it caches the data to a file as expected. But when I add a new route it's not added to the cache. Looking at the code I don't see anything that expires the cache (e.g. after X minutes/hours).

Is there a different method of working that I am missing here?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.