Generate a PHP script for faster routing :rocket:

Overview

jasny-banner

SwitchRoute

Build Status Scrutinizer Code Quality Code Coverage Infection MSI Packagist Stable Version Packagist License

Generating a PHP script for faster routing.

The traditional way of routing uses regular expressions. This method was improved by FastRoute, which compiles all routes to a single regexp. SwitchRoute abandons this completely, opting for a series of switch statements instead.

Processing the routes to produce the switch statements isn't particularly fast. However, the generation only need to happen when the routes change. Routing using the generated switch statements is up to 2x faster than with FastRoute using caching and up to 100x faster than any router not using caching.

============================= Average Case (path) =============================

SwitchRoute            100% | ████████████████████████████████████████████████████████████  |
FastRoute (cache)       59% | ███████████████████████████████████                           |
SwitchRoute (psr)       20% | ███████████                                                   |
Symfony                  2% | █                                                             |
FastRoute                1% |                                                               |
Laravel                  1% |                                                               |

See all benchmark results

Installation

composer require jasny/switch-route

Requires PHP 7.2+

Usage

In all examples we'll use the following function to get the routes;

function getRoutes(): array
{
    return [
        'GET      /'                  => ['controller' => 'InfoController'],

        'GET      /users'             => ['controller' => 'UserController', 'action' => 'listAction'],
        'POST     /users'             => ['controller' => 'UserController', 'action' => 'addAction'],
        'GET      /users/{id}'        => ['controller' => 'UserController', 'action' => 'getAction'],
        'POST|PUT /users/{id}'        => ['controller' => 'UserController', 'action' => 'updateAction'],
        'DELETE   /users/{id}'        => ['controller' => 'UserController', 'action' => 'deleteAction'],

        'GET      /users/{id}/photos' => ['action' => 'ListPhotosAction'],
        'POST     /users/{id}/photos' => ['action' => 'AddPhotosAction'],

        'POST     /export'            => ['include' => 'scripts/export.php'],
    ];
}

Both {id} and :id syntax are supported for capturing url path variables. If the segment can be anything, but doesn't need to be captured, use * (eg /comments/{id}/*).

Regular expressions on path variables (eg {id:\d+}) is not supported.

The path variables can be used arguments when invoking the action. Reflection is used to determine the name of the parameters, which are matched against the names of the path variables.

class UserController
{
    public function updateAction(string $id)
    {
        // ...
    }
}

Note that the path variables are always strings.

Pretty controller and action names

By default the controller and action should be configured with a fully qualified class name (includes namespace). However, it's possible to use a pretty name instead and have the Invoker convert it to an fqcn.

function getRoutes(): array
{
    return [
        'GET      /'                  => ['controller' => 'info'],

        'GET      /users'             => ['controller' => 'user', 'action' => 'list'],
        'POST     /users'             => ['controller' => 'user', 'action' => 'add'],
        'GET      /users/{id}'        => ['controller' => 'user', 'action' => 'get'],
        'POST|PUT /users/{id}'        => ['controller' => 'user', 'action' => 'update'],
        'DELETE   /users/{id}'        => ['controller' => 'user', 'action' => 'delete'],

        'GET      /users/{id}/photos' => ['action' => 'list-photos'],
        'POST     /users/{id}/photos' => ['action' => 'add-photos'],

        'POST     /export'            => ['include' => 'scripts/export.php'],
    ];
}

Pass a callable to the Invoker that converts the pretty controller and action names

$stud = fn($str) => strtr(ucwords($str, '-'), ['-' => '']);
$camel = fn($str) => strtr(lcfirst(ucwords($str, '-')), ['-' => '']);

$invoker = new Invoker(function (?string $controller, ?string $action) use ($stud, $camel) {
    return $controller !== null
        ? [$stud($controller) . 'Controller', $camel($action ?? 'default') . 'Action']
        : [$stud($action) . 'Action', '__invoke'];
});

Basic

By default the generator generates a function to route requests.

use Jasny\SwitchRoute\Generator;

// Always generate in development env, but not in production.
$overwrite = (getenv('APPLICATION_ENV') ?: 'dev') === 'dev';

$generator = new Generator();
$generator->generate('route', 'generated/route.php', 'getRoutes', $overwrite);

To route, include the generated file and call the route function.

require 'generated/route.php';

$method = $_SERVER["REQUEST_METHOD"];
$path = rawurldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));

route($method, $path);

PSR-15 compatible middleware

PSR-7 is an abstraction for HTTP Requests. It allows you to more easily test your application. It's available through http_message extension from pecl or one of the many PSR-7 libraries.

The related PSR-15 describes a way of processing ServerRequest objects through handlers and middleware. This makes it easier to abstract your application.

The library can generate a classes that implements PSR-15 MiddlewareInterface.

This generated route middleware will set the attributes of the ServerRequest based on the matched route. The generated invoke middleware will instantiate the controller and invoke the action.

This has been split into two steps, so you can add middleware that acts after the route is determined, but before it's invoked.

use Jasny\SwitchRoute\Generator;
use Jasny\SwitchRoute\Invoker;

// Always generate in development env, but not in production.
$overwrite = (getenv('APPLIACTION_ENV') ?: 'dev') === 'dev';

$routeGenerator = new Generator(new Generator\GenerateRouteMiddleware());
$routeGenerator->generate('App\Generated\RouteMiddleware', 'generated/RouteMiddleware.php', 'getRoutes', $overwrite);

$invoker = new Invoker();
$invokeGenerator = new Generator(new Generator\GenerateInvokeMiddleware($invoker));
$invokeGenerator->generate('App\Generated\InvokeMiddleware', 'generated/InvokeMiddleware.php', 'getRoutes', $overwrite);

Use any PSR-15 compatible request dispatcher, like Relay, to handle the request.

use App\Generated\RouteMiddleware;
use App\Generated\InvokeMiddleware;
use Jasny\SwitchRoute\NotFoundMiddleware;
use HttpMessage\Factory as HttpFactory;
use HttpMessage\ServerRequest;
use Relay\Relay;

$httpFactory = new HttpFactory();

$middleware[] = new RouteMiddleware();
$middleware[] = new NotFoundMiddleware($httpFactory);
$middleware[] = new InvokeMiddleware(fn($controllerClass) => new $controllerClass($httpFactory));

$relay = new Relay($middleware);

$request = new ServerRequest($_SERVER, $_COOKIE, $_QUERY, $_POST, $_FILES);
$response = $relay->handle($request);

You typically want to use a DI (dependency injection) container, optionally with autowiring, to create a controller rather than doing a simple new.

Error pages

If there is no route that matches against the current request URI, the generated script or invoker will give a 404 Not Found or 405 Method Not Allowed response. The 405 response given, when there is a matching endpoint, but none of the methods match. The response will contain a simple text body.

To change this behavior create a default route.

function getRoutes(): array
{
    return [
        'GET /'   => ['controller' => 'info'],
        // ...

        'default' => ['controller' => 'error', 'action' => 'not-found],
    ];
}

If the method should have array $allowedMethods as function parameter. If the array is empty, a 404 Not Found response should be given. Otherwise a 405 Method Not Allowed response may be given, including an Allow header with the allowed methods.

class ErrorController
{
    public function notFoundAction(array $allowedMethods)
    {
        if ($allowedMethods === []) {
            http_response_code(404);
        } else {
            http_response_code(405);
            header('Allow: ' . join(', ', $allowedMethods));
        }
        
        echo "<h1>Sorry, there is nothing here</h1>";
    }
}

This example shows a simple implementation that doesn't use the PSR-7 ServerRequest.

Pre-generated routing script

If the generated file already exists, the overhead of SwitchRoute is already minimal. To have zero overhead in a production environment, generate the classes or script in advance each time a new version is deployed.

Create a script bin/generate-router.php

require_once 'config/routes.php';

(new Jasny\SwitchRoute\Generator)->generate('route', 'generated/route.php', 'getRoutes', true);

Add it to composer.json so it's called every time after autoload is updated, which occurs by running composer update or composer install.

{
    "scripts": {
        "post-autoload-dump": [
            "bin/generate-router.php"
        ]
    }
}

Documentation

Generator

Generator is a service to generate a PHP script based on a set of routes.

Each route is a key pair, where the key is the HTTP method and URL path. The value is an array that should contain either a controller and (optionally) an action property OR an include property.

For routes with an include property, the script simply gets included. Using include provides a way to add routing to legacy applications.

For routes with a controller property, the controller will be instantiated and the action will be invoked, using parameter parsed from the URL as arguments.

The generator takes a callable as construction argument, which is used to generated the PHP code from structured routes. This is one the invokable Generator\Generate... objects from this library or a custom generation function.

new Generator(new Generator\GenerateRouteMiddleware());

A new GenerateFunction object is created if no generate callable is supplied (optional DI).

The class has a single method generate() and no public properties.

Generator::generate()

Generator::generate() will create a PHP script to route a request. The script is written to the specified file, which should be included via require (or autoload).

Generator::generate(string $name, string $filename, callable $getRoutes, bool $force)

The $name is either the name of the function or the name of the class being generated and may include a namespace.

Rather than passing the routes as argument, a callback is used which is called to get the routes. This callback isn't invoked if the router is not replaced.

The optional force parameter defaults to true, meaning that a new script is generated each time that this method is called. In a production environment, you should set this to false and delete the generated files each time you update your application.

If force is false and the file already exists, no new file is generated. It's recommended to have the opcache zend extension installed and enabled. This prevents additional checks on the file system for each request.

Generator\GenerateFunction

GenerateFunction is an invokable class which generated as function that will call the action or include the script specified by the route.

This class doesn't use PSR-7 or any other request abstraction. Instead it will directly instantiate the controller and invoke the action, passing the correct path segments as arguments.

You should pass an Invoker object when instantiating this invokable. If you don't, one will automatically be created during construction (optional DI).

When calling generate, you pass the name of the routing function. This may include a namespace.

use Jasny\SwitchRoute\Generator;
use Jasny\SwitchRoute\Invoker;

$invoker = new Invoker();
$generate = new Generator\GenerateFunction($invoker);

$generator = new Generator($generate);
$generator->generate('route', 'generated/route.php', 'getRoutes', true);

The generated function takes two arguments, first is the request method and second is the request path. The path isn't directly available in $_SERVER, but needs to be extracted from $_SERVER['REQUEST_URI'] which also contains the query string.

require 'generated/route.php';

$method = $_SERVER["REQUEST_METHOD"];
$path = rawurldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));

route($method, $path);

The function returns whatever is returned by the action method.

Generator\GenerateRouteMiddleware

Generator\GenerateRouteMiddleware is an invokable to generate a middleware class that will determine the route and set both the route properties and path variables as PSR-7 ServerRequest attributes. The middleware implements the PSR-15 MiddlewareInterface.

When calling generate, you pass the name of the middleware class. This may include a namespace.

use Jasny\SwitchRoute\Generator;

$generate = new Generator\GenerateRouteMiddleware();

$generator = new Generator($generate);
$generator->generate('RouteMiddleware', 'generated/RouteMiddleware.php', 'getRoutes', true);

The route is determined only using $request->getMethod() and $request->getUri()->getPath().

The middleware will take the attributes of the route, like controller and action and add them to the ServerRequest via $request->addAttribute(). The names are prefixed with route: to prevent potential collisions with other middleware. So action becomes attribute route:action.

Beyond the attributes required to invoke the action, additional attributes may be specified, which can be handled by custom middleware or by the controller. For instance something like auth containing the required an authorization level. Do note that these attributes are always prefixed with route:, so auth becomes route:auth.

Path variables are formatted as route:{...} (eg route:{id}). This means that they won't collide with route arguments. To specify a fixed value for an argument of the action, the route argument need to have these braces.

[
    'GET /users/{id}/photos'        => ['action' => 'ListPhotosAction', '{page}' => 1],
    'GET /users/{id}/photos/{page}' => ['action' => 'ListPhotosAction'],
];

The middleware will set the route:methods_allowed attribute if it matches the URL path, regardless of the HTTP request method. This is many useful for responding with 405 Method Not Allowed.

Generator\GenerateInvokeMiddleware

Generator\GenerateInvokeMiddleware is an invokable to generate a middleware class that invokes the action based on the ServerRequest attributes. The middleware implements the PSR-15 MiddlewareInterface.

You should pass an Invoker object when instantiating this invokable. If you don't, one will automatically be created during construction (optional DI).

When calling generate, you pass the name of the middleware class. This may include a namespace.

use Jasny\SwitchRoute\Generator;

$generate = new Generator\GenerateInvokeMiddleware();

$generator = new Generator($generate);
$generator->generate('InvokeMiddleware', 'generated/InvokeMiddleware.php', 'getRoutes', true);

The generated class takes a callable as single, optional, constructor argument. This callable is used to instantiate controllers or actions and allows you to do dependency injection. When omitted, a simple new statement is used.

The invoke middleware should be used after the generated route middleware. Other (custom) middleware may be added between these two, which can act based on the server request attributes set by the route middleware.

Note that all controller and action calls are generated at forehand. Request attributes like route:controller and route:action should not be modified.

For routes that specify an include attribute, the script will simply be included and no other method calls are made.

Either a default route should be specified or NotFoundMiddleware should used. If neither is done, the generated invoke middleware will throw a LogicException when there is no matching route.

NotFoundMiddleware

NotFoundMiddlware will give a 404 Not Found or 405 Method Not Allowed response if there is no matching route and no default route has been specified.

The middleware takes an object that implements PSR-17 ResponseFactoryInterface as constructor argument. This factory is used to create a response when there is no matching route.

Attribute route:allowed_methods determines the response code. If there are no allowed methods for the URL, a 404 response is given. If there are, a 405 is given instead.

The generated response will have a simple text body with the HTTP status reason phrase. In case of a 405 response, the middleware will also set the Allow header.

Invoker

The Invoker is generates snippets for invoking the action or including the file as stated in the selected route. This includes converting the controller and/or action attribute to a class name and possibly method name.

By default, the if the route has a controller property, use it as the class name. The action property is taken as method. It defaults to defaultAction.

If only an action property is present, the invoker will use that as class name. The class must define an invokable object.

You can change how the invokable class and method names are generated by passing a callback to the constructor. The can be used for instance convert pretty names to fully qualified class names (FQCN) for the the controller and action class.

$stud = fn($str) => strtr(ucwords($str, '-'), ['-' => '']);
$camel = fn($str) => strtr(lcfirst(ucwords($str, '-')), ['-' => '']);

$invoker = new Invoker(function (?string $controller, ?string $action) use ($stud, $camel) {
    return $controller !== null
        ? ['App\\' . $stud($controller) . 'Controller', $camel($action ?? 'default') . 'Action']
        : ['App\\' . $stud($action) . 'Action', '__invoke'];
});

The invoker uses reflection to determine if the method is static or not. If the method isn't static the cont

Reflection is also used to find out the names of the arguments of the invokables. Those names are matched with the names of the path variables (like {id} => $id).

NoInvoker

NoInvoker can be used instead of Invoker to return the matched route, rather than invoking the action. The purpose is mainly for benchmarking and testing.

This should only be used in combination with GenerateFunction. When using PSR-7, you can achieve something similar by only using route middleware and not invoke middleware.

use Jasny\SwitchRoute\Generator;
use Jasny\SwitchRoute\NoInvoker;

$invoker = new NoInvoker();
$generate = new Generator\GenerateFunction($invoker);

$generator = new Generator($generate);
$generator->generate('route', 'generated/route.php', 'getRoutes', true);

The result of the generated function is an array with 3 elements. The first contains the HTTP status, the second contains the route attributes and the third hold the path variables.

require 'generated/route.php';

$method = $_SERVER["REQUEST_METHOD"];
$path = rawurldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));

$routeInfo = route($method, $path);

switch ($routeInfo[0]) {
    case 404:
        // ... 404 Not Found
        break;
    case 405:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        break;
    case 200:
        $route = $routeInfo[1];
        $vars = $routeInfo[2];
        // ... invoke action based on $route using $vars
        break;
}

Customization

You may pass any callable when creating a Generator class. This callable should have the following signature;

use Jasny\SwitchRoute\Generator;

$generate = function (string $name, array $routes, array $structure): string {
    // ... custom logic
    return $generatedCode;
};

$generator = new Generator($generate);
$generator->generate('route', 'generated/route.php', 'getRoutes', true);

The $routes are gathered by the Generator by calling the $getRoutes callable. The structure calculated based on these routes, by splitting a route up into sections. Each leaf has a key "\0" and a Endpoint object as value.

Custom Invoker

The standard Invoker may be replaced by a class that implements InvokableInterface. This interface describes 2 methods; generateInvocation() and generateDefault().

generateInvocation() generates the code of instantiating and calling an action for a given route. It takes 3 arguments;

  • the matching route (as array)
  • a callback for generating code that converts a method parameter to a path segment via path variables
  • PHP code to instantiate class, where %s should be replaced with the classname
$invoker->generateInvocation($route, function ($name, $type = null, $default = null) { /* ... */ }, '(new %s)'); 

generateDefault() doesn't take any arguments and should return code for if there is no matching route and no default route has been generated. This method is not called when generating middleware.

Comments
  • Infection

    Infection

    opened by yaroslavche 5
  • Endpoint methods constants

    Endpoint methods constants

    Can Endpoint contain constants for allowed methods?

       #\Jasny\SwitchRoute\Endpoint
        const METHOD_HEAD = 'HEAD';
        const METHOD_GET = 'GET';
        const METHOD_POST = 'POST';
        const METHOD_PUT = 'PUT';
        const METHOD_PATCH = 'PATCH';
        const METHOD_DELETE = 'DELETE';
        const METHOD_PURGE = 'PURGE';
        const METHOD_OPTIONS = 'OPTIONS';
        const METHOD_TRACE = 'TRACE';
        const METHOD_CONNECT = 'CONNECT';
    

    In this case, we can simply use Endpoint :: METHOD_ * anywhere without fear of mis-naming. Also possible implement something like:

        #\Jasny\SwitchRoute\Endpoint::isAvailableMethod
        private function isAvailableMethod(string $method): bool
        {
            return $method === @constant(sprintf('%s::METHOD_%s', __CLASS__, $method));
        }
    

    and then check method and remove the initialization in uppercase:

        #\Jasny\SwitchRoute\Endpoint::withRoute
        public function withRoute(string $method, $route, array $vars): self
        {
            if (!$this->isAvailableMethod($method)) {
                throw new InvalidRouteException("Unknown route method '$method'", 1);
            }
            if (isset($this->routes[$method])) {
                throw new InvalidRouteException("Duplicate route for '$method {$this->path}'");
            }
    
            $copy = clone $this;
            $copy->routes[$method] = $route;
            $copy->vars[$method] = $vars;
    
            return $copy;
        }
    

    As you can see, this is also an example of why we also need to check a code (https://github.com/jasny/switch-route/pull/3#discussion_r318522671). But of course, you can add one more exception, but it looks good. Just specify a subtype by code.

    opened by yaroslavche 2
  • PhpParser for generating snippets

    PhpParser for generating snippets

    What do you think about using PHP-Parser for generating a code? For example:

    # src/Generator/AbstractGenerate.php
        protected function generateSwitchStmt(array $structure, int $level = 0): Switch_
        {
            $condition = new Coalesce(
                new ArrayDimFetch(new Variable('segments'), new LNumber($level)),
                new String_("\0")
            );
            $cases = [];
            foreach ($structure as $segment => $sub) {
                /** @var String_|null $caseCondition */
                $caseCondition = '*' === $segment ? null : new String_($segment);
                $caseStmts = [];
                if ($sub instanceof Endpoint) {
                    $endpointSwitchCases = [];
                    foreach ($sub->getUniqueRoutes() as [$methods, $route, $vars]) {
                        foreach ($methods as $method) {
                            $endpointSwitchCases[] = new Case_(
                                new String_($method),
                                [
                                    // generate route statement
                                ]
                            );
                        }
                    }
                    $endpointSwitchStmt = new Switch_(new Variable('method'), $endpointSwitchCases);
                    $caseStmts[] = $endpointSwitchStmt;
                } else {
                    $caseStmts[] = $this->generateSwitchStmt($sub, $level + 1);
                }
                $caseStmts[] = new Break_(new LNumber($level + 1));
                $cases[] = new Case_(
                    $caseCondition,
                    $caseStmts
                );
            }
            return new Switch_($condition, $cases);
        }
    

    And use:

        protected function generateSwitch(array $structure, int $level = 0): string
        {
            $factory = new BuilderFactory;
            $node = $factory->namespace('App\\SwitchRoute') // may be need wrap into function?
                ->addStmt($this->generateSwitchStmt($structure, $level))
                ->getNode();
            $code = (new Standard())->prettyPrintFile([$node]);
            return $code;
        }
    

    will produce something like this:

    <?php
    
    namespace App\SwitchRoute;
    
    switch ($segments[0] ?? '\000') {
        case '\000':
            switch ($method) {
                case 'GET':
                    break;
            }
            break 1;
        case 'users':
            switch ($segments[1] ?? '\000') {
                case '\000':
                    switch ($method) {
                        case 'GET':
                            break;
                        case 'POST':
                            break;
                    }
                    break 2;
                default:
                  ...
    

    Then can remove generateEndpoint method. Also need to handle generateNs and no more identations needed. If you interested, I can try to implement. But need your thoughts.

    opened by yaroslavche 2
  • QUESTION: switch-route /route/ ending with

    QUESTION: switch-route /route/ ending with "/" not the same as /route/

    Thanks for a very useful script. Tried this out as (and will use it in a project) it seemed logical that a switch approach would be a lot faster than traditional approaches to switching. In my usecase of 50 odd routes its in the order of magnitude faster :) .. than others including fastroute :)

    With regards to the routes a route of /user/shop/{id}/products/ do not correspond to url of /user/shop/cakestore/products/ .. not found but /user/shop/{id}/products does .. Is there a way to allow trailing slash in route so that route (example ) /user/shop/{shop}/products/ corresponds to /user/shop/cakestore/products/ AND /user/shop/{shop}/products/{id} corresponds to /user/shop/cakestore/products/45

    As it works now a trailing slash is identified as another segment..

    (I mainly route to a php file ['include' => 'products.php'] and need an array of variables in my case $params['{$vars[key]}'=>$segments[$vars[value]..] AND $params[0] for trimmed $path value )

    Might be good to consider adding variable access for include files.. but since the level of difficulty adding in the return for 'include' route is quite low in my case its not necessary.. BUT allow for trailing slash in routes would be great..

    I think the idea is brilliant to generate a route script like this..

    Cheers

    opened by lookingdown 1
  • Question about Scrutinizer configuration...

    Question about Scrutinizer configuration...

    Sorry to come out of nowhere with this, but I hope you don't mind if I ask a question about your scrutinizer.yml configuration.

    I contribute to a number of PHP projects that use Scrutinizer, and we're fed-up with its proprietary static checker. While we generally like Scrutinizer's UI for displaying issues, we would prefer to use PHPStan (and only PHPStan) for static checking. According to the Scrutinizer documentation, it is possible to specify any style checkers, and as long as they output the expected "checkstyle" format, they are compatible with Scrutinizer's reporting, and can complement (or replace) the native checkers performed by "php-scrutinizer-run".

    Amazingly, after doing a very targeted github search, your 3 repos are the only ones that seem to be attempting to configure phpstan this way.

    So, to get around to my question: does this work for you? I can't seem to figure out how this is supposed to surface in the Scrutinizer UI. I've set up my repo the same way (in a PR branch), and I know that my "phpstan-checkstyle.xml" is being generated and stuffed with errors, but I don't see any hint of the generated information in the Scrutinizer interface.

    I notice that you both use phpstan and run php-scrutinizer-run. Why is that? Are you also having trouble getting the phpstan reports to display?

    Sorry to bomb you at random with all these questions, and I'll understand if you choose not to get involved.

    opened by shmax 1
  • FQCN 'controller' by default

    FQCN 'controller' by default

    Also I saw disadvantage (IMO). I can't set FQCN for route.controller key. I.e., ['controller' => IndexController::class].

    You can, it just requires you to need to specify in the Invoker constructor. Maybe the default should be that you specify the FQCN. And other things as presets.

    Originally posted by @jasny in https://github.com/jasny/switch-route/pull/3#issuecomment-525699969

    opened by yaroslavche 0
  • Pass query parameters as action arguments

    Pass query parameters as action arguments

    It should be trivial to switch from a path variable to a query parameter (or vice-versa). Using different actions prevents having to deal with too many situations in one action.

    The syntax might be;

    [
        "GET /users?(page={page})"                 => ["action" => "list-users"],
        "GET /users?filter={filter}&(page={page})" => ["action" => "query-users"],
        "GET /users?search={search}&(page={page})" => ["action" => "search-users"],
    ]
    

    Using round braces means the query parameter is optional. In the above situation the list-users-filtered action is invoked if the request has a filter parameter.


    Challenge is that if all query parameters that aren't defined are ignored, the first route is also valid if there is a filter query parameter. Also if a request has both a search and filter query param, what should be used?

    opened by jasny 0
Releases(v0.2.0)
Owner
Arnold Daniels
Web and blockchain developer at @ltonetwork
Arnold Daniels
Routing - The Routing component maps an HTTP request to a set of configuration variables.

Routing Component The Routing component maps an HTTP request to a set of configuration variables. Getting Started $ composer require symfony/routing

Symfony 7.3k Jan 6, 2023
Convention based routing for PHP

Croute Convention based routing for PHP based on Symfony components. Croute is great because: You don't need to maintain a routing table Promotes cons

Michael O'Connell 12 Nov 9, 2021
PHP routing (like laravel) (not complete yet)

PHP Router (under construction) This repository contains routing classes that enables you to define your routes similar to laravel 8 routes. Features

Kareem M. Fouad 6 Jan 16, 2022
Flight routing is a simple, fast PHP router that is easy to get integrated with other routers.

The PHP HTTP Flight Router divineniiquaye/flight-routing is a HTTP router for PHP 7.1+ based on PSR-7 and PSR-15 with support for annotations, created

Divine Niiquaye Ibok 16 Nov 1, 2022
Ertuo: quick routing for PHP

Ertuo: quick routing for PHP Ertuo (anagram of "Route"), is a small PHP library that does routing better and faster than conventional regular expressi

Ertuo 29 Jul 19, 2022
PHPRouter is an easy-to-use, fast, and flexible PHP router package with express-style routing.

PHP-Router is a modern, fast, and adaptable composer package that provides express-style routing in PHP without a framework.

Ayodeji O. 4 Oct 20, 2022
Fast PSR-7 based routing and dispatch component including PSR-15 middleware, built on top of FastRoute.

Route This package is compliant with PSR-1, PSR-2, PSR-4, PSR-7, PSR-11 and PSR-15. If you notice compliance oversights, please send a patch via pull

The League of Extraordinary Packages 608 Dec 30, 2022
🔍 This is a collection of utilities for routing and loading components.

Router Utilities - PHP Introduction A day will come when I will write documentation for this library. Until then, you can use this library to create r

Utilities for PHP 5 Sep 20, 2022
Generate routes documentation

Laravel Route Documentation Maybe we are wondering where these routes lead or what could be their purpose? This package decides to solve this solution

Ahmet Barut 6 Nov 16, 2022
klein.php is a fast & flexible router for PHP 5.3+

Klein.php klein.php is a fast & flexible router for PHP 5.3+ Flexible regular expression routing (inspired by Sinatra) A set of boilerplate methods fo

null 2.6k Jan 7, 2023
PHP Router class - A simple Rails inspired PHP router class.

PHP Router class A simple Rails inspired PHP router class. Usage of different HTTP Methods REST / Resourceful routing Reversed routing using named rou

Danny van Kooten 565 Jan 8, 2023
Fast request router for PHP

FastRoute - Fast request router for PHP This library provides a fast implementation of a regular expression based router. Blog post explaining how the

Nikita Popov 4.7k Dec 23, 2022
Pux is a fast PHP Router and includes out-of-box controller tools

Pux Pux is a faster PHP router, it also includes out-of-box controller helpers. 2.0.x Branch Build Status (This branch is under development) Benchmark

Yo-An Lin 1.3k Dec 21, 2022
A web router implementation for PHP.

Aura.Router Powerful, flexible web routing for PSR-7 requests. Installation and Autoloading This package is installable and PSR-4 autoloadable via Com

Aura for PHP 469 Jan 1, 2023
:tada: Release 2.0 is released! Very fast HTTP router for PHP 7.1+ (incl. PHP8 with attributes) based on PSR-7 and PSR-15 with support for annotations and OpenApi (Swagger)

HTTP router for PHP 7.1+ (incl. PHP 8 with attributes) based on PSR-7 and PSR-15 with support for annotations and OpenApi (Swagger) Installation compo

Sunrise // PHP 151 Jan 5, 2023
:bird: Simple PHP router

Macaw Macaw is a simple, open source PHP router. It's super small (~150 LOC), fast, and has some great annotated source code. This class allows you to

Noah Buscher 895 Dec 21, 2022
Toro is a PHP router for developing RESTful web applications and APIs.

Toro Toro is a PHP router for developing RESTful web applications and APIs. It is designed for minimalists who want to get work done. Quick Links Offi

Kunal Anand 1.2k Dec 27, 2022
A PHP rewrite of HackRouter by Facebook

Hack-Routing Fast, type-safe request routing, parameter retrieval, and link generation. It's a port of hack-router By Facebook, Inc. Components HTTP E

Saif Eddin Gmati 25 Aug 2, 2021
A lightweight and simple object oriented PHP Router

bramus/router A lightweight and simple object oriented PHP Router. Built by Bram(us) Van Damme (https://www.bram.us) and Contributors Features Support

Bramus! 935 Jan 1, 2023