{"id":13545519,"url":"https://github.com/bramus/router","last_synced_at":"2025-05-14T15:00:30.182Z","repository":{"id":38238801,"uuid":"9284517","full_name":"bramus/router","owner":"bramus","description":"A lightweight and simple object oriented PHP Router","archived":false,"fork":false,"pushed_at":"2024-03-12T19:08:04.000Z","size":173,"stargazers_count":1100,"open_issues_count":51,"forks_count":245,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-04-11T06:15:17.252Z","etag":null,"topics":["php","routing"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bramus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2013-04-07T22:44:51.000Z","updated_at":"2025-04-10T06:16:29.000Z","dependencies_parsed_at":"2024-06-17T22:46:49.457Z","dependency_job_id":null,"html_url":"https://github.com/bramus/router","commit_stats":{"total_commits":164,"total_committers":22,"mean_commits":7.454545454545454,"dds":0.3414634146341463,"last_synced_commit":"8e1d3875b196524bd4c283f18315d873420d9c28"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bramus%2Frouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bramus%2Frouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bramus%2Frouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bramus%2Frouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bramus","download_url":"https://codeload.github.com/bramus/router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254168158,"owners_count":22026104,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["php","routing"],"created_at":"2024-08-01T11:01:04.765Z","updated_at":"2025-05-14T15:00:30.089Z","avatar_url":"https://github.com/bramus.png","language":"PHP","funding_links":[],"categories":["PHP","php"],"sub_categories":[],"readme":"# bramus/router\n\n[![Build Status](https://github.com/bramus/router/workflows/CI/badge.svg)](https://github.com/bramus/router/actions) [![Source](http://img.shields.io/badge/source-bramus/router-blue.svg?style=flat-square)](https://github.com/bramus/router) [![Version](https://img.shields.io/packagist/v/bramus/router.svg?style=flat-square)](https://packagist.org/packages/bramus/router) [![Downloads](https://img.shields.io/packagist/dt/bramus/router.svg?style=flat-square)](https://packagist.org/packages/bramus/router/stats) [![License](https://img.shields.io/packagist/l/bramus/router.svg?style=flat-square)](https://github.com/bramus/router/blob/master/LICENSE)\n\nA lightweight and simple object oriented PHP Router.\nBuilt by Bram(us) Van Damme _([https://www.bram.us](https://www.bram.us))_ and [Contributors](https://github.com/bramus/router/graphs/contributors)\n\n\n## Features\n\n- Supports `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH` and `HEAD` request methods\n- [Routing shorthands such as `get()`, `post()`, `put()`, …](#routing-shorthands)\n- [Static Route Patterns](#route-patterns)\n- Dynamic Route Patterns: [Dynamic PCRE-based Route Patterns](#dynamic-pcre-based-route-patterns) or [Dynamic Placeholder-based Route Patterns](#dynamic-placeholder-based-route-patterns)\n- [Optional Route Subpatterns](#optional-route-subpatterns)\n- [Supports `X-HTTP-Method-Override` header](#overriding-the-request-method)\n- [Subrouting / Mounting Routes](#subrouting--mounting-routes)\n- [Allowance of `Class@Method` calls](#classmethod-calls)\n- [Custom 404 handling](#custom-404)\n- [Before Route Middlewares](#before-route-middlewares)\n- [Before Router Middlewares / Before App Middlewares](#before-router-middlewares)\n- [After Router Middleware / After App Middleware (Finish Callback)](#after-router-middleware--run-callback)\n- [Works fine in subfolders](#subfolder-support)\n\n\n\n## Prerequisites/Requirements\n\n- PHP 5.3 or greater\n- [URL Rewriting](https://gist.github.com/bramus/5332525)\n\n\n\n## Installation\n\nInstallation is possible using Composer\n\n```\ncomposer require bramus/router ~1.6\n```\n\n\n\n## Demo\n\nA demo is included in the `demo` subfolder. Serve it using your favorite web server, or using PHP 5.4+'s built-in server by executing `php -S localhost:8080` on the shell. A `.htaccess` for use with Apache is included.\n\nAdditionally a demo of a mutilingual router is also included. This can be found in the `demo-multilang` subfolder and can be ran in the same manner as the normal demo.\n\n## Usage\n\nCreate an instance of `\\Bramus\\Router\\Router`, define some routes onto it, and run it.\n\n```php\n// Require composer autoloader\nrequire __DIR__ . '/vendor/autoload.php';\n\n// Create Router instance\n$router = new \\Bramus\\Router\\Router();\n\n// Define routes\n// ...\n\n// Run it!\n$router-\u003erun();\n```\n\n\n### Routing\n\nHook __routes__ (a combination of one or more HTTP methods and a pattern) using `$router-\u003ematch(method(s), pattern, function)`:\n\n```php\n$router-\u003ematch('GET|POST', 'pattern', function() { … });\n```\n\n`bramus/router` supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` _(see [note](#a-note-on-making-head-requests))_, and `OPTIONS` HTTP request methods. Pass in a single request method, or multiple request methods separated by `|`.\n\nWhen a route matches against the current URL (e.g. `$_SERVER['REQUEST_URI']`), the attached __route handling function__ will be executed. The route handling function must be a [callable](http://php.net/manual/en/language.types.callable.php). Only the first route matched will be handled. When no matching route is found, a 404 handler will be executed.\n\n### Routing Shorthands\n\nShorthands for single request methods are provided:\n\n```php\n$router-\u003eget('pattern', function() { /* ... */ });\n$router-\u003epost('pattern', function() { /* ... */ });\n$router-\u003eput('pattern', function() { /* ... */ });\n$router-\u003edelete('pattern', function() { /* ... */ });\n$router-\u003eoptions('pattern', function() { /* ... */ });\n$router-\u003epatch('pattern', function() { /* ... */ });\n```\n\nYou can use this shorthand for a route that can be accessed using any method:\n\n```php\n$router-\u003eall('pattern', function() { … });\n```\n\nNote: Routes must be hooked before `$router-\u003erun();` is being called.\n\nNote: There is no shorthand for `match()` as `bramus/router` will internally re-route such requrests to their equivalent `GET` request, in order to comply with RFC2616 _(see [note](#a-note-on-making-head-requests))_.\n\n### Route Patterns\n\nRoute Patterns can be static or dynamic:\n\n- __Static Route Patterns__ contain no dynamic parts and must match exactly against the `path` part of the current URL.\n- __Dynamic Route Patterns__ contain dynamic parts that can vary per request. The varying parts are named __subpatterns__ and are defined using either Perl-compatible regular expressions (PCRE) or by using __placeholders__\n\n#### Static Route Patterns\n\nA static route pattern is a regular string representing a URI. It will be compared directly against the `path` part of the current URL.\n\nExamples:\n\n-  `/about`\n-  `/contact`\n\nUsage Examples:\n\n```php\n// This route handling function will only be executed when visiting http(s)://www.example.org/about\n$router-\u003eget('/about', function() {\n    echo 'About Page Contents';\n});\n```\n\n#### Dynamic PCRE-based Route Patterns\n\nThis type of Route Patterns contain dynamic parts which can vary per request. The varying parts are named __subpatterns__ and are defined using regular expressions.\n\nExamples:\n\n- `/movies/(\\d+)`\n- `/profile/(\\w+)`\n\nCommonly used PCRE-based subpatterns within Dynamic Route Patterns are:\n\n- `\\d+` = One or more digits (0-9)\n- `\\w+` = One or more word characters (a-z 0-9 _)\n- `[a-z0-9_-]+` = One or more word characters (a-z 0-9 _) and the dash (-)\n- `.*` = Any character (including `/`), zero or more\n- `[^/]+` = Any character but `/`, one or more\n\nNote: The [PHP PCRE Cheat Sheet](https://courses.cs.washington.edu/courses/cse154/15sp/cheat-sheets/php-regex-cheat-sheet.pdf) might come in handy.\n\nThe __subpatterns__ defined in Dynamic PCRE-based Route Patterns are converted to parameters which are passed into the route handling function. Prerequisite is that these subpatterns need to be defined as __parenthesized subpatterns__, which means that they should be wrapped between parens:\n\n```php\n// Bad\n$router-\u003eget('/hello/\\w+', function($name) {\n    echo 'Hello ' . htmlentities($name);\n});\n\n// Good\n$router-\u003eget('/hello/(\\w+)', function($name) {\n    echo 'Hello ' . htmlentities($name);\n});\n```\n\nNote: The leading `/` at the very beginning of a route pattern is not mandatory, but is recommended.\n\nWhen multiple subpatterns are defined, the resulting __route handling parameters__ are passed into the route handling function in the order they are defined in:\n\n```php\n$router-\u003eget('/movies/(\\d+)/photos/(\\d+)', function($movieId, $photoId) {\n    echo 'Movie #' . $movieId . ', photo #' . $photoId;\n});\n```\n\n#### Dynamic Placeholder-based Route Patterns\n\nThis type of Route Patterns are the same as __Dynamic PCRE-based Route Patterns__, but with one difference: they don't use regexes to do the pattern matching but they use the more easy __placeholders__ instead. Placeholders are strings surrounded by curly braces, e.g. `{name}`. You don't need to add parens around placeholders.\n\nExamples:\n\n- `/movies/{id}`\n- `/profile/{username}`\n\nPlaceholders are easier to use than PRCEs, but offer you less control as they internally get translated to a PRCE that matches any character (`.*`).\n\n```php\n$router-\u003eget('/movies/{movieId}/photos/{photoId}', function($movieId, $photoId) {\n    echo 'Movie #' . $movieId . ', photo #' . $photoId;\n});\n```\n\nNote: the name of the placeholder does not need to match with the name of the parameter that is passed into the route handling function:\n\n```php\n$router-\u003eget('/movies/{foo}/photos/{bar}', function($movieId, $photoId) {\n    echo 'Movie #' . $movieId . ', photo #' . $photoId;\n});\n```\n\n\n### Optional Route Subpatterns\n\nRoute subpatterns can be made optional by making the subpatterns optional by adding a `?` after them. Think of blog URLs in the form of `/blog(/year)(/month)(/day)(/slug)`:\n\n```php\n$router-\u003eget(\n    '/blog(/\\d+)?(/\\d+)?(/\\d+)?(/[a-z0-9_-]+)?',\n    function($year = null, $month = null, $day = null, $slug = null) {\n        if (!$year) { echo 'Blog overview'; return; }\n        if (!$month) { echo 'Blog year overview'; return; }\n        if (!$day) { echo 'Blog month overview'; return; }\n        if (!$slug) { echo 'Blog day overview'; return; }\n        echo 'Blogpost ' . htmlentities($slug) . ' detail';\n    }\n);\n```\n\nThe code snippet above responds to the URLs `/blog`, `/blog/year`, `/blog/year/month`, `/blog/year/month/day`, and `/blog/year/month/day/slug`.\n\nNote: With optional parameters it is important that the leading `/` of the subpatterns is put inside the subpattern itself. Don't forget to set default values for the optional parameters.\n\nThe code snipped above unfortunately also responds to URLs like `/blog/foo` and states that the overview needs to be shown - which is incorrect. Optional subpatterns can be made successive by extending the parenthesized subpatterns so that they contain the other optional subpatterns: The pattern should resemble `/blog(/year(/month(/day(/slug))))` instead of the previous `/blog(/year)(/month)(/day)(/slug)`:\n\n```php\n$router-\u003eget('/blog(/\\d+(/\\d+(/\\d+(/[a-z0-9_-]+)?)?)?)?', function($year = null, $month = null, $day = null, $slug = null) {\n    // ...\n});\n```\n\nNote: It is highly recommended to __always__ define successive optional parameters.\n\nTo make things complete use [quantifiers](http://www.php.net/manual/en/regexp.reference.repetition.php) to require the correct amount of numbers in the URL:\n\n```php\n$router-\u003eget('/blog(/\\d{4}(/\\d{2}(/\\d{2}(/[a-z0-9_-]+)?)?)?)?', function($year = null, $month = null, $day = null, $slug = null) {\n    // ...\n});\n```\n\n\n### Subrouting / Mounting Routes\n\nUse `$router-\u003emount($baseroute, $fn)` to mount a collection of routes onto a subroute pattern. The subroute pattern is prefixed onto all following routes defined in the scope. e.g. Mounting a callback `$fn` onto `/movies` will prefix `/movies` onto all following routes.\n\n```php\n$router-\u003emount('/movies', function() use ($router) {\n\n    // will result in '/movies/'\n    $router-\u003eget('/', function() {\n        echo 'movies overview';\n    });\n\n    // will result in '/movies/id'\n    $router-\u003eget('/(\\d+)', function($id) {\n        echo 'movie id ' . htmlentities($id);\n    });\n\n});\n```\n\nNesting of subroutes is possible, just define a second `$router-\u003emount()` in the callable that's already contained within a preceding `$router-\u003emount()`.\n\n\n### `Class@Method` calls\n\nWe can route to the class action like so:\n\n```php\n$router-\u003eget('/(\\d+)', '\\App\\Controllers\\User@showProfile');\n```\n\nWhen a request matches the specified route URI, the `showProfile` method on the `User` class will be executed. The defined route parameters will be passed to the class method.\n\nThe method can be static (recommended) or non-static (not-recommended). In case of a non-static method, a new instance of the class will be created.\n\nIf most/all of your handling classes are in one and the same namespace, you can set the default namespace to use on your router instance via `setNamespace()`\n\n```php\n$router-\u003esetNamespace('\\App\\Controllers');\n$router-\u003eget('/users/(\\d+)', 'User@showProfile');\n$router-\u003eget('/cars/(\\d+)', 'Car@showProfile');\n```\n\n### Custom 404\n\nThe default 404 handler sets a 404 status code and exits. You can override this default 404 handler by using `$router-\u003eset404(callable);`\n\n```php\n$router-\u003eset404(function() {\n    header('HTTP/1.1 404 Not Found');\n    // ... do something special here\n});\n```\n\nYou can also define multiple custom routes e.x. you want to define an `/api` route, you can print a custom 404 page:\n\n```php\n$router-\u003eset404('/api(/.*)?', function() {\n    header('HTTP/1.1 404 Not Found');\n    header('Content-Type: application/json');\n\n    $jsonArray = array();\n    $jsonArray['status'] = \"404\";\n    $jsonArray['status_text'] = \"route not defined\";\n\n    echo json_encode($jsonArray);\n});\n```\n\nAlso supported are `Class@Method` callables:\n\n```php\n$router-\u003eset404('\\App\\Controllers\\Error@notFound');\n```\n\nThe 404 handler will be executed when no route pattern was matched to the current URL.\n\n💡 You can also manually trigger the 404 handler by calling `$router-\u003etrigger404()`\n\n```php\n$router-\u003eget('/([a-z0-9-]+)', function($id) use ($router) {\n    if (!Posts::exists($id)) {\n        $router-\u003etrigger404();\n        return;\n    }\n\n    // …\n});\n```\n\n\n### Before Route Middlewares\n\n`bramus/router` supports __Before Route Middlewares__, which are executed before the route handling is processed.\n\nLike route handling functions, you hook a handling function to a combination of one or more HTTP request methods and a specific route pattern.\n\n```php\n$router-\u003ebefore('GET|POST', '/admin/.*', function() {\n    if (!isset($_SESSION['user'])) {\n        header('location: /auth/login');\n        exit();\n    }\n});\n```\n\nUnlike route handling functions, more than one before route middleware is executed when more than one route match is found.\n\n\n### Before Router Middlewares\n\nBefore route middlewares are route specific. Using a general route pattern (viz. _all URLs_), they can become __Before Router Middlewares__ _(in other projects sometimes referred to as before app middlewares)_ which are always executed, no matter what the requested URL is.\n\n```php\n$router-\u003ebefore('GET', '/.*', function() {\n    // ... this will always be executed\n});\n```\n\n\n### After Router Middleware / Run Callback\n\nRun one (1) middleware function, name the __After Router Middleware__ _(in other projects sometimes referred to as after app middlewares)_ after the routing was processed. Just pass it along the `$router-\u003erun()` function. The run callback is route independent.\n\n```php\n$router-\u003erun(function() { … });\n```\n\nNote: If the route handling function has `exit()`ed the run callback won't be run.\n\n\n### Overriding the request method\n\nUse `X-HTTP-Method-Override` to override the HTTP Request Method. Only works when the original Request Method is `POST`. Allowed values for `X-HTTP-Method-Override` are `PUT`, `DELETE`, or `PATCH`.\n\n\n### Subfolder support\n\nOut-of-the box `bramus/router` will run in any (sub)folder you place it into … no adjustments to your code are needed. You can freely move your _entry script_ `index.php` around, and the router will automatically adapt itself to work relatively from the current folder's path by mounting all routes onto that __basePath__.\n\nSay you have a server hosting the domain `www.example.org` using `public_html/` as its document root, with this little _entry script_ `index.php`:\n\n```php\n$router-\u003eget('/', function() { echo 'Index'; });\n$router-\u003eget('/hello', function() { echo 'Hello!'; });\n```\n\n- If your were to place this file _(along with its accompanying `.htaccess` file or the like)_ at the document root level (e.g. `public_html/index.php`), `bramus/router` will mount all routes onto the domain root (e.g. `/`) and thus respond to `https://www.example.org/` and `https://www.example.org/hello`.\n\n- If you were to move this file _(along with its accompanying `.htaccess` file or the like)_ into a subfolder (e.g. `public_html/demo/index.php`), `bramus/router` will mount all routes onto the current path (e.g. `/demo`) and thus repsond to `https://www.example.org/demo` and `https://www.example.org/demo/hello`. There's **no** need for `$router-\u003emount(…)` in this case.\n\n#### Disabling subfolder support\n\nIn case you **don't** want `bramus/router` to automatically adapt itself to the folder its being placed in, it's possible to manually override the _basePath_ by calling `setBasePath()`. This is necessary in the _(uncommon)_ situation where your _entry script_ and your _entry URLs_ are not tightly coupled _(e.g. when the entry script is placed into a subfolder that does not need be part of the URLs it responds to)_.\n\n```php\n// Override auto base path detection\n$router-\u003esetBasePath('/');\n\n$router-\u003eget('/', function() { echo 'Index'; });\n$router-\u003eget('/hello', function() { echo 'Hello!'; });\n\n$router-\u003erun();\n```\n\nIf you were to place this file into a subfolder (e.g. `public_html/some/sub/folder/index.php`), it will still mount the routes onto the domain root (e.g. `/`) and thus respond to `https://www.example.org/` and `https://www.example.org/hello` _(given that your `.htaccess` file – placed at the document root level – rewrites requests to it)_\n\n## Integration with other libraries\n\nIntegrate other libraries with `bramus/router` by making good use of the `use` keyword to pass dependencies into the handling functions.\n\n```php\n$tpl = new \\Acme\\Template\\Template();\n\n$router-\u003eget('/', function() use ($tpl) {\n    $tpl-\u003eload('home.tpl');\n    $tpl-\u003esetdata(array(\n        'name' =\u003e 'Bramus!'\n    ));\n});\n\n$router-\u003erun(function() use ($tpl) {\n    $tpl-\u003edisplay();\n});\n```\n\nGiven this structure it is still possible to manipulate the output from within the After Router Middleware\n\n\n## A note on working with PUT\n\nThere's no such thing as `$_PUT` in PHP. One must fake it:\n\n```php\n$router-\u003eput('/movies/(\\d+)', function($id) {\n\n    // Fake $_PUT\n    $_PUT  = array();\n    parse_str(file_get_contents('php://input'), $_PUT);\n\n    // ...\n\n});\n```\n\n\n## A note on making HEAD requests\n\nWhen making `HEAD` requests all output will be buffered to prevent any content trickling into the response body, as defined in [RFC2616 (Hypertext Transfer Protocol -- HTTP/1.1)](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4):\n\n\u003e The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.\n\nTo achieve this, `bramus/router` but will internally re-route `HEAD` requests to their equivalent `GET` request and automatically suppress all output.\n\n\n## Unit Testing \u0026 Code Coverage\n\n`bramus/router` ships with unit tests using [PHPUnit](https://github.com/sebastianbergmann/phpunit/).\n\n- If PHPUnit is installed globally run `phpunit` to run the tests.\n\n- If PHPUnit is not installed globally, install it locally throuh composer by running `composer install --dev`. Run the tests themselves by calling `vendor/bin/phpunit`.\n\n  The included `composer.json` will also install `php-code-coverage` which allows one to generate a __Code Coverage Report__. Run `phpunit --coverage-html ./tests-report` (XDebug required), a report will be placed into the `tests-report` subfolder.\n\n\n## Acknowledgements\n\n`bramus/router` is inspired upon [Klein](https://github.com/chriso/klein.php), [Ham](https://github.com/radiosilence/Ham), and [JREAM/route](https://bitbucket.org/JREAM/route) . Whilst Klein provides lots of features it is not object oriented. Whilst Ham is Object Oriented, it's bad at _separation of concerns_ as it also provides templating within the routing class. Whilst JREAM/route is a good starting point it is limited in what it does (only GET routes for example).\n\n\n\n## License\n\n`bramus/router` is released under the MIT public license. See the enclosed `LICENSE` for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbramus%2Frouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbramus%2Frouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbramus%2Frouter/lists"}