{"id":36370330,"url":"https://github.com/vertilia/router","last_synced_at":"2026-01-11T14:00:03.750Z","repository":{"id":57077599,"uuid":"186311567","full_name":"vertilia/router","owner":"vertilia","description":"Highly efficient multi-purpose router library","archived":false,"fork":false,"pushed_at":"2023-06-20T16:14:19.000Z","size":97,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-28T09:36:43.609Z","etag":null,"topics":["api","controller","openapi","optimization-algorithms","performance","router","routing","speed","tree-structure"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vertilia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-12T22:40:21.000Z","updated_at":"2023-05-16T12:03:58.000Z","dependencies_parsed_at":"2024-11-14T18:49:44.606Z","dependency_job_id":null,"html_url":"https://github.com/vertilia/router","commit_stats":{"total_commits":20,"total_committers":2,"mean_commits":10.0,"dds":0.09999999999999998,"last_synced_commit":"4de4bcf5e0f495c44ba51ee86da8c162d4cc7e2c"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/vertilia/router","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vertilia%2Frouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vertilia%2Frouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vertilia%2Frouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vertilia%2Frouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vertilia","download_url":"https://codeload.github.com/vertilia/router/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vertilia%2Frouter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28306983,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T11:18:18.743Z","status":"ssl_error","status_checked_at":"2026-01-11T11:07:56.842Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","controller","openapi","optimization-algorithms","performance","router","routing","speed","tree-structure"],"created_at":"2026-01-11T14:00:03.625Z","updated_at":"2026-01-11T14:00:03.703Z","avatar_url":"https://github.com/vertilia.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# router\n\nA lightweight http routing library tuned for performance, allowing translation of request path into controller name and\nvalidation of path parameters as defined in\n[OpenAPI specification](http://spec.openapis.org/oas/v3.0.2#patterned-fields) and (partly) in\n[URI Template](https://www.rfc-editor.org/rfc/rfc6570).\n\nA highly efficient routing mechanism is especially useful in large routing tables since the number of operations to find\nthe controller depends on the routing tree levels and not on the total number of routes in the list.\n\nSimply saying, instead of looking up all available paths represented as a list of regular expressions, we construct a\nrouting tree of folders from this list of paths and only visit the levels corresponding to current request. This leads\nus step by step to the correct route instead of blindly scanning the whole list of available routes until the matching\nroute is found (or not). Also, the time to find incorrect routes is minimized, we don't need to scan the whole list to\nfind out that a route is missing.\n\nPeople regularly researching their access logs to discover the most frequent requests to put them higher in routes list\nwill appreciate the functionality. These researches are now a history, and they may put their precious time on something\nmore important.\n\nThe router algorithm is universal, in a sense that when defining routing rules, you can attach any structure to the leaf\nnode, and it will be returned when corresponding route is provided. This leaf node is represented by an array containing\nat least `controller` element with the name of operation that will be launched by the user-land code when the route is\nfound. If requested path contained parameters, they will be detected and injected in returned array as `parameters`\nelement.\n\nSince we believe in high effectiveness of [`ValidArray`](https://github.com/vertilia/valid-array)-based\n`HttpRequestInterface` structure in context of request handling, we also provide an `HttpRequestRouter` class allowing\nautomatic registration of route parameters in request object, their validation via filters provided in leaf elements and\neasy access via array notation as in `$request['param']`. These parameters are guaranteed to be valid following the\nuser-specified format (see tests).\n\nHigh efficiency of filtering mechanism is backed by php-native [`filter` extension](https://php.net/filter).\n\n# Usage\n\n## Universal router\n\nWhen instantiating a universal router you need to pass the [`HttpParserInterface`](https://github.com/vertilia/parser)\nparser object (which will translate the parameters placeholders to regexps) and a list of routing files.\n\n```php\n\u003c?php // public/index.php (v1)\n\n$router = new Vertilia\\Router\\Router(\n    new Vertilia\\Parser\\OpenApiParser(),\n    [__DIR__ . '/../etc/routes.php']\n);\n```\n\n## HttpRequest router\n\nIn most cases you are working in CGI context, so you'll likely use an `HttpRequestRouter` class. This one will use\n`OpenApiParser` by default, so you will not need to inject this one, but instead you will provide an `HttpRequest`,\nfrom which the router will retrieve the route to lookup:\n\n```php\n\u003c?php // public/index.php (v2)\n\n$router = new Vertilia\\Router\\HttpRouter(\n    new Vertilia\\Request\\HttpRequest(),\n    [__DIR__ . '/../etc/routes.php']\n);\n```\n\n## Routing file\n\nEach routing file is a valid php script returning an array with routing information, where each entry specifies\na leaf node corresponding to a route. In its simplest form it's just a controller name for specific route:\n\n```php\n\u003c?php // etc/routes.php\n\nreturn [\n    'GET /'              =\u003e App\\Controller\\IndexController::class,\n    'GET /products'      =\u003e App\\Controller\\ProductsController::class,\n    'GET /products/{id}' =\u003e App\\Controller\\ProductsController::class,\n];\n```\n\nThe more complex form may be used to provide filtering information for specific route, or any other information you will\nneed. In this case instead of a string with controller name an associative array is used. The only required element here\nis `controller`, which will store the name of controller your code is looking for. Other elements may be defined as\nconvenient:\n- `controller` - to provide controller name (used by both `Router` and `HttpRequestRouter`)\n- `filters` - to provide filters for detected parameters (used by `HttpRequestRouter`)\n- `responses` - to provide an array or responses per status code (for example to implement\n  [OpenApi responses](https://swagger.io/docs/specification/describing-responses/))\n\n```php\n\u003c?php // etc/routes.php\n\nreturn [\n    'GET /'              =\u003e App\\Controller\\IndexController::class,\n    'GET /products'      =\u003e App\\Controller\\ProductsController::class,\n    'GET /products/{id}' =\u003e [\n        'controller' =\u003e App\\Controller\\ProductsController::class,\n        'filters' =\u003e [\n            'id' =\u003e FILTER_VALIDATE_INT,\n        ],\n    ],\n    'PUT /products/{id}' =\u003e [\n        'controller' =\u003e App\\Controller\\ProductsController::class,\n        'filters' =\u003e [\n            'id' =\u003e [\n                'filter'  =\u003e FILTER_VALIDATE_INT,\n                'options' =\u003e ['min_range' =\u003e 1],\n            ],\n            'description' =\u003e FILTER_SANITIZE_STRING,\n            'image' =\u003e [\n                'filter' =\u003e FILTER_VALIDATE_URL,\n                'flags'  =\u003e FILTER_FLAG_HOST_REQUIRED,\n            ],\n        ],\n    ],\n];\n```\n\nThis form (with `filters` element) is required if you use `HttpRequestRouter` version of the router, which uses filters\nfrom leaf element to store and validate detected path parameters in `HttpRequest` object.\n\nIn the last route filters are provided not only for path parameter `id` but also for `description` and `image`\nparameters that may come from other sources, like query, cookies, http body or headers. All of them will be filtered\naccordingly and accessible inside the `HttpRequest` object in the form of, for example, `$request['description']`.\n\nIf filter for path parameter is not provided explicitly, `parameters` element will still be injected into the returned\nleaf element containing all detected path parameters, but these parameters will not be validated nor registered in\n`HttpResponse` object and user will need to validate their values by other means.\n\nYes, you need to implement the controllers and make them available via `composer` autoloader or other mechanism. This is\nout of scope of routing library, but we have a real-world example of router use [below](#example).\n\nAlso, it's up to you to decide in which form controller names are provided to the application, whether it is a class\nname as we use in our examples, or method names, or function names, or even a partial string that will be completed\nlater. Implement it the way you like. We prefer the method described above, since it has several advantages. Class names\nreferencable via `::class` constants make it simpler to type using IDE code completion. Also, they are more error-prone,\nsince renaming a class with your IDE will either automatically rename the controller name in route file or at least\ndisplay it as non-existent in there. You will not need to wait the integration or even deployment phase to discover the\nundefined exception. And yes, the optimisation phase described below will convert it to strings anyway.\n\n## Providing content MIME type\n\nTo be able to set different controllers and validations for different incoming content types it is also possible to\nprovide content MIME type when defining the route, like in:\n```\nGET /products/{id} application/json\n```\nIn this case content type-specific routes take precedence over generic routes, like in\n```\nGET /products/{id}\n```\n\nIf the route could not be found in content type-aware form, it is searched in generic routes.\n\n## Optimisation of routes parsing\n\nWhen loading routing tables each route must be split to identify its method, path and MIME type (if present), then the\npath is analyzed to distinguish static paths from paths with variables, and then paths with variables are replaced by\na tree structure where regular expressions allow to recognize parameters and catch variables values.\n\nThis parsing process is executed on each request, so when the number of routes is elevated, it may start to weight\nconsiderably on performance.\n\nTo go faster, we can completely bypass the parsing stage on each request by pre-compiling the routing table and save\nit in a native php file. Loading it on each request will take no time with active opcode caching.\n\nTo use pre-compiling method:\n\n- use provided `vendor/bin/routec` route compiler script that takes a list of routes files, parses them and stores the\n  resulting structure in a form understandable by the `Router::setParsedRoutes` method;\n- save this structure to the `.php` file, ex: `routes-generated.php`;\n- in your `index.php`, on router instantiation, omit the `$routes` parameter to the constructor and load pre-compiled\n  routes tree via `$router-\u003esetParsedRoutes(include 'routes-generated.php')` instead.\n\n### Example\n\nThis script needs to be executed every time the routes file is updated to translate `etc/routes.php` file into\n`cache/routes-generated.php`:\n\n```shell\nvendor/bin/routec etc/routes.php \u003ecache/routes-generated.php\n```\n\nYou may provide several input files if your routes are split between them. `routec` tool will output a final file with\nall routes combined.\n\nOn each request we don't need to parse the whole list of routes since we use already cached structure from\n`cache/routes-generated.php`:\n\n```php\n\u003c?php // www/index.php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse App\\Controller\\NotFoundController;\nuse Vertilia\\Request\\HttpRequest;\nuse Vertilia\\Router\\HttpRouter;\n\n// construct route from the request\n$request = new HttpRequest(\n    $_SERVER,\n    $_GET,\n    $_POST,\n    $_COOKIE,\n    $_FILES,\n    file_get_contents('php://input')\n);\n\n// instantiate HttpRouter without parsing\n$router = new HttpRouter($request);\n\n// set pre-compiled routes\n$router-\u003esetParsedRoutes(include __DIR__ . '/../cache/routes-generated.php');\n\n// get controller name and parameters from request\n// use NotFoundController as default\n$target = $router-\u003egetControllerFromRequest(NotFoundController::class);\n\n// instantiate controller with request\n$controller = new ($target['controller'])();\n\n// let controller do its work and output corresponding response\n$controller-\u003erender();\n```\n\n### Limitations of optimization techniques\n\nPlease be aware of the following caveats when going this way:\n\n1. ⚠️ Php constants that you may use to define input filters (like `FILTER_VALIDATE_INT`, `FILTER_FLAG_HOST_REQUIRED`\n   etc.) are normally exported as their numeric values. `routec` tool is trying to restore constants names from these\n   values. These values may change from version to version of php binary, so try to generate the exported routes file\n   using the same version that will be used with `setParsedRoutes()` call. `routec` will do its best trying to replace\n   these numeric values by their respective constants in optimized routes file, but it's in your interests to verify the\n   result in the optimized file. Pay special attention to flags sharing the same integer value,\n   like `FILTER_FLAG_IPV4`, `FILTER_FLAG_HOSTNAME` and `FILTER_FLAG_EMAIL_UNICODE`, or flags not available in all php\n   versions, like `FILTER_FLAG_GLOBAL_RANGE`.\n\n2. ⚠️ Also, if you use validation callbacks (`FILTER_CALLBACK` or `ValidArray::FILTER_EXTENDED_CALLBACK` filters), they\n   will not be exported at all, and you'll need to manually copy these callbacks from initial routes file.\n\n# Sample `petstore.yaml` specification\n\nAPI specification file for this example is available from\n[OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml) GitHub\nrepository.\n\nRouting file corresponding to the specification is as follows:\n\n```php\n\u003c?php // etc/routes.php\n\nuse Vertilia\\ValidArray\\ValidArray;\n\nreturn [\n    'GET /pets' =\u003e [\n        'controller' =\u003e 'findPets',\n        'filters' =\u003e [\n            'limit' =\u003e FILTER_VALIDATE_INT,\n            'tags'  =\u003e [\n                'filter'  =\u003e ValidArray::FILTER_EXTENDED_CALLBACK,\n                'flags'   =\u003e FILTER_REQUIRE_SCALAR,\n                'options' =\u003e ['callback' =\u003e function($v) {return explode(',', $v);}],\n            ],\n        ],\n    ],\n    'POST /pets application/json' =\u003e [\n        'controller' =\u003e 'addPet',\n        'filters' =\u003e [\n            'name' =\u003e FILTER_DEFAULT,\n            'tag'  =\u003e FILTER_DEFAULT,\n        ],\n    ],\n    'GET /pets/{id}' =\u003e [\n        'controller' =\u003e 'find_pet_by_id',\n        'filters' =\u003e [\n            'id' =\u003e FILTER_VALIDATE_INT,\n        ],\n    ],\n    'DELETE /pets/{id}' =\u003e [\n        'controller' =\u003e 'deletePet',\n        'filters' =\u003e [\n            'id' =\u003e FILTER_VALIDATE_INT,\n        ],\n    ],\n];\n```\n\n## Routing format reference\n\nRecognized format:\n\n- `[ROUTE =\u003e LEAF_STRUCTURE, …]`\n\nROUTE is a string containing 2 or 3 parts delimited by space character:\n- `METHOD` `PATH`\n- `METHOD` `PATH` `CONTENT_TYPE`\n\nParts of a ROUTE:\n- `METHOD` is an HTTP request method\n- `PATH` is an HTTP request path component which may contain `{variable}` placeholders representing path parameters\n- `CONTENT_TYPE` is an HTTP request Content-Type header (if provided in request)\n\nIf Content-Type header is provided within a request, and corresponding 3-parts route is not found, search is repeated\nwith corresponding 2-parts route without `CONTENT_TYPE` part.\n\nROUTE examples:\n- `GET /`\n- `POST /api/login application-json`\n- `GET /api/users/{id}/posts`\n\nLEAF_STRUCTURE is returned when corresponding route is found. May be of 2 types:\n- scalar, ex: `\"UserResponse\"`\n- array, ex: `[\"controller\" =\u003e \"LoginResponse\", ...other custom elements]`\n\nIf scalar form is used for LEAF_STRUCTURE (like `\"UserResponse\"`), it is translated internally during parsing phase into\nan array with a single `controller` element: `[\"controller\" =\u003e \"UserResponse\"]`. When LEAF_STRUCTURE is returned after\nthe route is identified, it is always returned as an array.\n\n#### Examples of correct routes\n\n```php\n[\n    \"GET /\" =\u003e \"IndexResponse\",\n    \"POST /api/users/me/login application-json\" =\u003e \"UserLoginResponse\",\n    \"GET /api/users/{id}/posts\" =\u003e [\n        \"controller\" =\u003e \"UserPostsResponse\",\n        \"filters\" =\u003e [\n            \"id\" =\u003e FILTER_VALIDATE_INT\n        ]\n    ]\n]\n```\n\n#### LEAF_STRUCTURE returned for corresponding requests\n\n| Request METHOD PATH TYPE                    | Returned leaf structure                                                                                                                               |\n|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `GET /`                                     | `[\"controller\" =\u003e \"IndexResponse\"]`                                                                                                                   |\n| `POST /api/users/me/login application-json` | `[\"controller\" =\u003e \"UserLoginResponse\"]`                                                                                                               |\n| `GET /api/users/42/posts`                   | \u003cpre\u003e[\u003cbr/\u003e  \"controller\" =\u003e \"UserPostsResponse\",\u003cbr/\u003e  \"filters\" =\u003e [\"id\" =\u003e FILTER_VALIDATE_INT],\u003cbr/\u003e  \"parameters\" =\u003e [\"id\" =\u003e \"42\"],\u003cbr/\u003e]\u003c/pre\u003e |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvertilia%2Frouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvertilia%2Frouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvertilia%2Frouter/lists"}