{"id":15068282,"url":"https://github.com/thiagodp/router","last_synced_at":"2025-04-10T16:33:21.943Z","repository":{"id":65373273,"uuid":"579723923","full_name":"thiagodp/router","owner":"thiagodp","description":"🚦 ExpressJS-like router for PHP","archived":false,"fork":false,"pushed_at":"2025-02-26T22:45:11.000Z","size":122,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T14:21:18.380Z","etag":null,"topics":["expressjs","http","php","router"],"latest_commit_sha":null,"homepage":"","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/thiagodp.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":"2022-12-18T17:32:22.000Z","updated_at":"2025-03-18T18:38:43.000Z","dependencies_parsed_at":"2025-02-17T14:33:49.891Z","dependency_job_id":"ec7b5900-6b8a-48e2-a59a-8389a621b68f","html_url":"https://github.com/thiagodp/router","commit_stats":{"total_commits":28,"total_committers":1,"mean_commits":28.0,"dds":0.0,"last_synced_commit":"e99cbc961d2fc5b0fcfe8c0709e4825407247123"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagodp%2Frouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagodp%2Frouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagodp%2Frouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagodp%2Frouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thiagodp","download_url":"https://codeload.github.com/thiagodp/router/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248252718,"owners_count":21072701,"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":["expressjs","http","php","router"],"created_at":"2024-09-25T01:33:07.061Z","updated_at":"2025-04-10T16:33:21.932Z","avatar_url":"https://github.com/thiagodp.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Version](https://poser.pugx.org/phputil/router/v?style=flat-square)](https://packagist.org/packages/phputil/router)\n![Build](https://github.com/thiagodp/router/actions/workflows/ci.yml/badge.svg?style=flat)\n[![License](https://poser.pugx.org/phputil/router/license?style=flat-square)](https://packagist.org/packages/phputil/router)\n\n# phputil/router\n\n\u003e ExpressJS-like router for PHP\n\n- No third-party dependencies\n- Unit-tested\n- Mockable - it's easy to create automated tests for your API\n\n👉 Do **NOT** use it in production yet - just for toy projects.\n\n## Installation\n\n\u003e Requires PHP 7.4+\n\n```bash\ncomposer require phputil/router\n```\n\n👉 You may also like to install [phputil/cors](https://github.com/thiagodp/cors).\n\n### Notes\n\n- Unlike ExpressJS, `phputil/router` needs an HTTP server to run (if the request is not [mocked](#mocking-an-http-request)). You can use the HTTP server of your choice, such as `php -S localhost:80`, [Apache](https://httpd.apache.org/), [Nginx](https://nginx.org/) or [http-server](https://www.npmjs.com/package/http-server).\n  - See [Server Configuration](server.md) for more information.\n- If you are using Apache or Nginx, you may need to inform the `rootURL` parameter when calling `listen()`. Example:\n    ```php\n    // Sets the 'rootURL' to where the index.php is located.\n    $app-\u003elisten( [ 'rootURL' =\u003e dirname( $_SERVER['PHP_SELF'] ) ] );\n    ```\n\n## Examples\n\n### Hello World\n\n```php\nrequire_once 'vendor/autoload.php';\nuse \\phputil\\router\\Router;\n\n$app = new Router();\n$app-\u003eget( '/', function( $req, $res ) {\n    $res-\u003esend( 'Hello World!' );\n} );\n$app-\u003elisten();\n```\n\n### Using parameters\n\n```php\nrequire_once 'vendor/autoload.php';\nuse \\phputil\\router\\Router;\n\n$app = new Router();\n$app-\u003eget( '/', function( $req, $res ) {\n        $res-\u003esend( 'Hi, Anonymous' );\n    } )\n    -\u003eget( '/:name', function( $req, $res ) {\n        $res-\u003esend( 'Hi, ' . $req-\u003eparam( 'name' ) );\n    } )\n    -\u003eget( '/json/:name', function( $req, $res ) {\n        $res-\u003ejson( [ 'hi' =\u003e $req-\u003eparam( 'name' ) ] );\n    } );\n$app-\u003elisten();\n```\n\n### Middleware per route\n\n```php\nrequire_once 'vendor/autoload.php';\nuse \\phputil\\router\\Router;\n\n$middlewareIsAdmin = function( $req, $res, \u0026$stop ) {\n    session_start();\n    $isAdmin = isset( $_SESSION[ 'admin' ] ) \u0026\u0026 $_SESSION[ 'admin' ];\n    if ( $isAdmin ) {\n        return; // Access allowed\n    }\n    $stop = true;\n    $res-\u003estatus( 403 )-\u003esend( 'Admin only' ); // Forbidden\n};\n\n$app = new Router();\n$app-\u003eget( '/admin', $middlewareIsAdmin, function( $req, $res ) {\n    $res-\u003esend( 'Hello, admin' );\n} );\n$app-\u003elisten();\n```\n\n\n[See all the examples](https://github.com/thiagodp/router/tree/main/examples/)\n\n\u003e ℹ Interested in helping us? Submit a Pull Request with a new example or open an Issue with your code.\n\n## Features\n\n- [✔] Support to standard HTTP methods (`GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `OPTIONS`) and `PATCH`.\n- [✔] Route parameters\n    - _e.g._ `$app-\u003eget('/customers/:id', function( $req, $res ) { $res-\u003esend( $req-\u003eparam('id') ); } );`\n- [✔] URL groups\n    - _e.g._ `$app-\u003eroute('/customers/:id')-\u003eget('/emails', $cbGetEmails );`\n- [✔] Global middlewares\n    - _e.g._ `$app-\u003euse( function( $req, $res, \u0026$stop ) { /*...*/ } );`\n- [✔] Middlewares per URL group\n    - _e.g._ `$app-\u003eroute( '/admin' )-\u003euse( $middlewareIsAdmin )-\u003eget( '/', function( $req, $res ) { /*...*/ } );`\n- [✔] Middlewares per route\n    - _e.g._ `$app-\u003eget( '/', $middleware1, $middleware2, function( $req, $res ) { /*...*/ } );`\n- [✔] Request cookies\n    - _e.g._ `$app-\u003eget('/', function( $req, $res ) { $res-\u003esend( $req-\u003ecookie('sid') ); } );`\n- [✔] _Extra_: Can mock HTTP requests for testing, without the need to running an HTTP server.\n- [🕑] _(soon)_ Deal with `multipart/form-data` on `PUT` and `PATCH`\n\n\n## Known Middlewares\n\n- [phputil/cors](https://github.com/thiagodp/cors) - [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) Middleware\n- [phputil/csrf](https://github.com/thiagodp/phputil-csrf) - Anti [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) Middleware\n\n\u003e ℹ Did you create a useful middleware? Open an Issue for including it here.\n\n\n## API\n\nThis library does not aim to cover the entire [ExpressJS API](https://expressjs.com/en/api.html). However, feel free to contribute to this project and add more features.\n\nTypes:\n- [Middleware](#middleware)\n- [Router](#router)\n- [RouterOptions](#routeroptions)\n- [HttpRequest](#httprequest)\n- [ExtraData](#extradata)\n- [HttpResponse](#httpresponse)\n\n\n### Middleware\n\nIn `phputil/router`, a middleware is a function that:\n\n1. Perform some action (e.g., set response headers, verify permissions) _before_ a route is evaluated.\n2. Can stop the router, optionally setting a response.\n\nSyntax:\n```php\nfunction ( HttpRequest $req, HttpResponse $res, bool \u0026$stop = false )\n```\nwhere:\n- `$req` allows to _get_ all the _request_ headers and data.\n- `$res` allows to _set_ all the _response_ headers and data.\n- `$stop` allows to stop the router, when set to `true`.\n\n\n### Router\n\n\u003e Class that represents a router.\n\n#### get\n\nMethod that deals with a `GET` HTTP request.\n\n```php\nfunction get( string $route, callable ...$callbacks )\n```\nwhere:\n- `$route` is a route (path).\n- `$callbacks` can receive none, one or more [middleware](#middleware) functions and one route handler - which must be the last function.\n\nA route handler has the following syntax:\n```php\nfunction ( HttpRequest $req, HttpResponse $res )\n```\nwhere:\n- `$req` allows to _get_ all the _request_ headers and data.\n- `$res` allows to _set_ all the _response_ headers and data.\n\nExamples:\n```php\nuse \\phputil\\router\\HttpRequest;\nuse \\phputil\\router\\HttpResponse;\n\n$app-\u003e\n    get( '/hello', function( HttpRequest $req, HttpResponse $res ) {\n        $res-\u003esend( 'Hello!' );\n    } )\n    get( '/world',\n        // Middleware\n        function( HttpRequest $req, HttpResponse $res, bool \u0026$stop ) {\n            if ( $req-\u003eheader( 'Origin' ) === 'http://localhost' ) {\n                $res-\u003estatus( 200 )-\u003esend( 'World!' );\n                $stop = true;\n            }\n        },\n        // Route handler\n        function( HttpRequest $req, HttpResponse $res ) {\n            $res-\u003estatus( 400 )-\u003esend( 'Error: not in http://localhost :(' );\n        }\n    );\n```\n\n#### post\n\nMethod that deals with a `POST` HTTP request. Same syntax as [get](#get)'s.\n\n#### put\n\nMethod that deals with a `PUT` HTTP request. Same syntax as [get](#get)'s.\n\n#### delete\n\nMethod that deals with a `DELETE` HTTP request. Same syntax as [get](#get)'s.\n\n#### head\n\nMethod that deals with a `HEAD` HTTP request. Same syntax as [get](#get)'s.\n\n#### option\n\nMethod that deals with a `OPTION` HTTP request. Same syntax as [get](#get)'s.\n\n#### patch\n\nMethod that deals with a `PATCH` HTTP request. Same syntax as [get](#get)'s.\n\n#### all\n\nMethod that deals with any HTTP request. Same syntax as [get](#get)'s.\n\n#### group\n\nAlias to the method [route](#route).\n\n#### route\n\nMethod that adds a route group, where you can register one or more HTTP method handlers.\n\nExample:\n```php\n$app-\u003e\n    route( '/employees' )\n        -\u003eget( '/emails', function( $req, $res ) { /* GET /employees/emails  */ } )\n        -\u003eget( '/phone-numbers', function( $req, $res ) { /* GET /employees/phone-numbers */ } )\n        -\u003epost( '/children', function( $req, $res ) { /* POST /employees/children */ } )\n        -\u003eend() // Finishes the group and back to \"/\"\n    -\u003eget( '/customers', function( $req, $res ) { /* GET /customers */ } )\n    ;\n```\n\nIMPORTANT: Don't forget to finish a route/group with the method `end()`.\n\n#### end\n\nMethod that finishes a route group and returns to the group parent.\n\nExample:\n```php\n$app-\u003e\n    route( '/products' )\n        -\u003eget( '/colors', function( $req, $res ) { /* GET /products/colors  */ } )\n        -\u003eroute( '/suppliers' )\n            -\u003eget( '/emails', function( $req, $res ) { /* GET /products/suppliers/emails */ } )\n            -\u003eend() // Finishes \"/suppliers\" and back to \"/products\"\n        -\u003eget( '/sizes', function( $req, $res ) { /* GET /products/sizes  */ } )\n        -\u003eend() // Finishes \"/products\" and back to \"/\"\n    -\u003eget( '/sales', function( $req, $res ) { /* GET /sales  */ } )\n    ;\n```\n\n#### use\n\nMethod that adds a [middleware](#middleware) to be evaluated before the routes declared after it.\n\nExample:\n```php\n$app\n    -\u003euse( $myMiddlewareFunction )\n    -\u003eget( '/hello', $sayHelloFunction ); // Executes after the middleware\n```\n\n#### listen\n\nMethod that executes the router.\n\n```php\nfunction listen( array|RouterOptions $options = [] ): void\n```\nOptions are:\n- `rootURL` is a string that sets the root URL. Example: `dirname( $_SERVER['PHP_SELF'] )`. By default it is `''`.\n- `req` is an object that implements the interface `HttpRequest`, which retrieves all the headers and data from a HTTP request. _Changing it is only useful if you want to unit test your API_ - see [Mocking an HTTP request](#mocking-an-http-request). By default, it will receive an object from the class `RealHttpRequest`.\n- `res` is an object that implements the interface `HttpResponse`. _You probably won't need to change its value_. By default, it will receive an object from the class `RealHttpResponse`.\n\nExample:\n```php\n// Sets the 'rootURL' to where the index.php is located.\n$app-\u003elisten( [ 'rootURL' =\u003e dirname( $_SERVER['PHP_SELF'] ) ] );\n```\n\nYou can also use an instance of `RouterOptions` for setting the options:\n```php\nuse phputil\\router\\RouterOptions;\n// Sets the 'rootURL' to where the index.php is located.\n$app-\u003elisten( ( new RouterOptions() )-\u003ewithRootURL( dirname( $_SERVER['PHP_SELF'] ) ) );\n```\n\n\n### RouterOptions\n\n\u003e Options for the [Router](#router)'s [listen()](#listen) method.\n\n#### withRootURL\n\n```php\nwithRootURL( string $url ): RouterOptions\n```\n\n#### withReq\n\n```php\nwithReq( HttpRequest $req ): RouterOptions\n```\n\n#### withRes\n\n```php\nwithRes( HttpResponse $res ): RouterOptions\n```\n\n\n### HttpRequest\n\n\u003e Interface that represents an HTTP request.\n\nAPI:\n\n```php\ninterface HttpRequest {\n\n    /** Returns the current URL or `null` on failure. */\n    function url(): ?string;\n\n    /** Returns the current URL without any queries. E.g. `/foo?bar=10` -\u003e `/foo` */\n    function urlWithoutQueries(): ?string;\n\n    /** Returns the URL queries. E.g. `/foo?bar=10\u0026zoo=A` -\u003e `['bar'=\u003e'10', 'zoo'=\u003e'A']` */\n    function queries(): array;\n\n    /** Returns all HTTP request headers */\n    function headers(): array;\n\n    /** Returns the header with the given case-insensitive name, or `null` if not found. */\n    function header( $name ): ?string;\n\n    /** Returns the raw body or `null` on failure. */\n    function rawBody(): ?string;\n\n    /**\n     * Returns the converted content, depending on the `Content-Type` header:\n     *   - For `x-www-form-urlencoded`, it returns an `array`;\n     *   - For `application/json`, it returns an `object` or an `array` (depending on the content).\n     *   - Otherwise it returns a `string`, or `null` on failure.\n     */\n    function body();\n\n    /** Returns the HTTP request method or `null` on failure. */\n    function method(): ?string;\n\n    /** Returns all cookies as an array (map). */\n    function cookies(): array;\n\n    /**\n     * Returns the cookie value with the given case-insensitive key or `null` if not found.\n     *\n     * @param string $key Cookie key.\n     * @return string|null\n     */\n    function cookie( $key ): ?string;\n\n    /**\n     * Returns a URL query or route parameter with the given name (key),\n     * or `null` when the given name is not found.\n     *\n     * @param string $name Parameter name.\n     * @return string\n     */\n    function param( $name ): ?string;\n\n    /**\n     * Returns all the URL queries and route parameters as an array (map).\n     * @return array\n     */\n    function params(): array;\n\n    /**\n     * Returns extra, user-configurable data.\n     * @return ExtraData\n     */\n    function extra(): ExtraData;\n\n}\n\n```\n\n\n### ExtraData\n\n\u003e Extra, user-defined data.\n\n```php\nclass ExtraData {\n\n    /**\n     * Sets a value to the given key. Chainable method.\n     *\n     * @param string|int $key\n     * @param mixed $value\n     * @return ExtraData\n     */\n    function set( $key, $value ): ExtraData;\n\n    /**\n     * Returns the value for the given key, or null otherwise.\n     * @param string|int $key\n     * @return mixed\n     */\n    function get( $key );\n\n    /**\n     * Returns the keys and values as an array.\n     */\n    function toArray(): array;\n\n}\n```\n\n\n### HttpResponse\n\n\u003e Interface that represents an HTTP response.\n\nMost of its methods are chainable, that is, you can call them in a sequence. Example:\n```php\n$response-\u003estatus( 201 )-\u003esend( 'Saved successfully.' );\n```\n\nAPI:\n```php\ninterface HttpResponse {\n\n    /**\n     * Sets the HTTP status code.\n     *\n     * @param int $code HTTP status code.\n     * @return HttpResponse\n     */\n    function status( int $code ): HttpResponse;\n\n    /**\n     * Indicates if the current HTTP status code is equal to the given one.\n     *\n     * @param int $code HTTP status code.\n     * @return bool\n     */\n    function isStatus( int $code ): bool;\n\n    /**\n     * Sets an HTTP header.\n     *\n     * @param string $header HTTP header.\n     * @param string|int|float|bool|array $value Header value.\n     * @return HttpResponse\n     */\n    function header( string $header, $value ): HttpResponse;\n\n    /**\n     * Indicates if the response has the given HTTP header.\n     *\n     * @param string $header HTTP header.\n     * @return boolean\n     */\n    function hasHeader( string $header ): bool;\n\n    /**\n     * Returns the response header, if it exists. Returns `null` otherwise.\n     *\n     * @param string $header HTTP header.\n     * @return string|null\n     */\n    function getHeader( string $header ): ?string;\n\n    /**\n     * Removes a header.\n     *\n     * @param string $header Header to remove.\n     */\n    function removeHeader( string $header ): void;\n\n    /**\n     * Sets a redirect response.\n     *\n     * @param int $statusCode HTTP status code.\n     * @param string|null $path Path.\n     * @return HttpResponse\n     */\n    function redirect( int $statusCode, $path = null ): HttpResponse;\n\n    /**\n     * Sets a cookie.\n     *\n     * @param string $name Name (key)\n     * @param string $value Value.\n     * @param array $options Optional map with the following options:\n     *  - `domain`: string\n     *  - `path`: string\n     *  - `httpOnly`: true|1\n     *  - `secure`: true|1\n     *  - `maxAge`: int\n     *  - `expires`: string\n     *  - `sameSite`: true|1\n     * @return HttpResponse\n     *\n     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies for options' meanings.\n     */\n    function cookie( string $name, string $value, array $options = [] ): HttpResponse;\n\n    /**\n     * Clears a cookie with the given name (key).\n     *\n     * @param string $name Name (key)\n     * @param array $options Optional map with the same options as #cookie()'s.\n     * @return HttpResponse\n     */\n    function clearCookie( string $name, array $options = [] ): HttpResponse;\n\n    /**\n     * Sets the `Content-Type` header with the given MIME type.\n     *\n     * @param string $mime MIME type.\n     * @return HttpResponse\n     */\n    function type( string $mime ): HttpResponse;\n\n    /**\n     * Sends the given HTTP response body.\n     *\n     * @param mixed $body Response body.\n     * @return HttpResponse\n     */\n    function send( $body ): HttpResponse;\n\n    /**\n     * Sends a file based on its path.\n     *\n     * @param string $path File path\n     * @param array $options Optional map with the options:\n     *  - `mime`: string - MIME type, such as `application/pdf`.\n     * @return HttpResponse\n     */\n    function sendFile( string $path, array $options = [] ): HttpResponse;\n\n    /**\n     * Send the given content as JSON, also setting the needed headers.\n     *\n     * @param mixed $body Content to send as JSON.\n     * @return HttpResponse\n     */\n    function json( $body ): HttpResponse;\n\n    /**\n     * Ends the HTTP response.\n     *\n     * @param bool $clear If it is desired to clear the headers and the body after sending them. It defaults to `true`.\n     */\n    function end( bool $clear = true ): HttpResponse;\n}\n```\n\n\n### Mocking an HTTP request\n\n👉 Useful for API testing\n\n```php\nrequire_once 'vendor/autoload.php';\nuse \\phputil\\router\\FakeHttpRequest;\nuse \\phputil\\router\\Router;\n$app = new Router();\n\n// Set a expectation\n$app-\u003eget( '/foo', function( $req, $res ) { $res-\u003esend( 'Called!' ); } );\n\n// Mock the request\n$fakeReq = new FakeHttpRequest();\n$fakeReq-\u003ewithURL( '/foo' )-\u003ewithMethod( 'GET' );\n\n// Use the mock request\n$app-\u003elisten( [ 'req' =\u003e $fakeReq ] ); // It will use the fake request to call \"/foo\"\n```\n\n## License\n\n[MIT](LICENSE) © [Thiago Delgado Pinto](https://github.com/thiagodp)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagodp%2Frouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthiagodp%2Frouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagodp%2Frouter/lists"}