phputil / router
类似ExpressJS的PHP路由器
Requires
- php: >=7.4
- ext-json: *
- ext-mbstring: *
- ext-pcre: *
Requires (Dev)
- kahlan/kahlan: ^5.2
- phpstan/phpstan: ^1.9
README
phputil/router
类似ExpressJS的PHP路由器
- 没有第三方依赖
- 单元测试
- 可模拟 - 容易为您的API创建自动化测试
👉 目前请不要在生产环境中使用 - 仅适用于玩具项目。
安装
需要PHP 7.4+
composer require phputil/router
👉 您可能还希望安装 phputil/cors。
注意事项
- 与ExpressJS不同,
phputil/router
需要HTTP服务器才能运行(如果请求没有被模拟)。您可以使用您选择的HTTP服务器,例如php -S localhost:80
、Apache、Nginx或http-server。- 有关更多信息,请参阅服务器配置。
- 如果您正在使用Apache或Nginx,在调用
listen()
时可能需要通知rootURL
参数。示例// Sets the 'rootURL' to where the index.php is located. $app->listen( [ 'rootURL' => dirname( $_SERVER['PHP_SELF'] ) ] );
示例
你好,世界
require_once 'vendor/autoload.php'; use \phputil\router\Router; $app = new Router(); $app->get( '/', function( $req, $res ) { $res->send( 'Hello World!' ); } ); $app->listen();
使用参数
require_once 'vendor/autoload.php'; use \phputil\router\Router; $app = new Router(); $app->get( '/', function( $req, $res ) { $res->send( 'Hi, Anonymous' ); } ) ->get( '/:name', function( $req, $res ) { $res->send( 'Hi, ' . $req->param( 'name' ) ); } ) ->get( '/json/:name', function( $req, $res ) { $res->json( [ 'hi' => $req->param( 'name' ) ] ); } ); $app->listen();
每个路由的中间件
require_once 'vendor/autoload.php'; use \phputil\router\Router; $middlewareIsAdmin = function( $req, $res, &$stop ) { session_start(); $isAdmin = isset( $_SESSION[ 'admin' ] ) && $_SESSION[ 'admin' ]; if ( $isAdmin ) { return; // Access allowed } $stop = true; $res->status( 403 )->send( 'Admin only' ); // Forbidden }; $app = new Router(); $app->get( '/admin', $middlewareIsAdmin, function( $req, $res ) { $res->send( 'Hello, admin' ); } ); $app->listen();
ℹ 帮助我们提供一个示例,只需提交一个拉取请求或打开一个带有代码的问题。
特性
- [✔] 支持标准HTTP方法(
GET
、POST
、PUT
、DELETE
、HEAD
、OPTIONS
)和PATCH
。 - [✔] 路由参数
- 例如:
$app->get('/customers/:id', function( $req, $res ) { $res->send( $req->param('id') ); } );
- 例如:
- [✔] URL组
- 例如:
$app->route('/customers/:id')->get('/emails', $cbGetEmails );
- 例如:
- [✔] 全局中间件
- 例如:
$app->use( function( $req, $res, &$stop ) { /*...*/ } );
- 例如:
- [✔] 每个URL组的中间件
- 例如:
$app->route( '/admin' )->use( $middlewareIsAdmin )->get( '/', function( $req, $res ) { /*...*/ } );
- 例如:
- [✔] 每个路由的中间件
- 例如:
$app->get( '/', $middleware1, $middleware2, function( $req, $res ) { /*...*/ } );
- 例如:
- [✔] 请求cookies
- 例如:
$app->get('/', function( $req, $res ) { $res->send( $req->cookie('sid') ); } );
- 例如:
- [✔] 额外:可以模拟HTTP请求进行测试,无需运行HTTP服务器。
- [🕑] (即将推出) 处理PUT和PATCH上的
multipart/form-data
已知中间件
- phputil/cors - CORS中间件
- phputil/csrf - 反CSRF中间件
ℹ 您创建了一个有用的中间件吗?打开一个问题以包括它在这里。
API
这个库的目标不是涵盖整个ExpressJS API。然而,您可以自由地为这个项目做出贡献并添加更多功能。
类型
中间件
In phputil/router
,中间件是一个函数,它
- 在评估路由之前执行一些操作(例如,设置响应头,验证权限)。
- 可以停止路由器,可选地设置响应。
语法
function ( HttpRequest $req, HttpResponse $res, bool &$stop = false )
其中
$req
允许获取所有 请求 头和数据。$res
允许设置所有 响应 头和数据。$stop
当设置为true
时允许停止路由器。
路由器
表示路由器的类。
get
处理 GET
HTTP 请求的方法。
function get( string $route, callable ...$callbacks )
其中
$route
是一个路由(路径)。$callbacks
可以接收零个、一个或多个 中间件 函数和一个路由处理程序 - 必须是最后一个函数。
路由处理程序具有以下语法
function ( HttpRequest $req, HttpResponse $res )
其中
$req
允许获取所有 请求 头和数据。$res
允许设置所有 响应 头和数据。
示例
use \phputil\router\HttpRequest; use \phputil\router\HttpResponse; $app-> get( '/hello', function( HttpRequest $req, HttpResponse $res ) { $res->send( 'Hello!' ); } ) get( '/world', // Middleware function( HttpRequest $req, HttpResponse $res, bool &$stop ) { if ( $req->header( 'Origin' ) === 'http://localhost' ) { $res->status( 200 )->send( 'World!' ); $stop = true; } }, // Route handler function( HttpRequest $req, HttpResponse $res ) { $res->status( 400 )->send( 'Error: not in http://localhost :(' ); } );
post
处理 POST
HTTP 请求的方法。与 get 的语法相同。
put
处理 PUT
HTTP 请求的方法。与 get 的语法相同。
delete
处理 DELETE
HTTP 请求的方法。与 get 的语法相同。
head
处理 HEAD
HTTP 请求的方法。与 get 的语法相同。
option
处理 OPTION
HTTP 请求的方法。与 get 的语法相同。
patch
处理 PATCH
HTTP 请求的方法。与 get 的语法相同。
all
处理任何 HTTP 请求的方法。与 get 的语法相同。
group
是 route 方法的别名。
route
添加路由组的方法,其中可以注册一个或多个 HTTP 方法处理程序。
示例
$app-> route( '/employees' ) ->get( '/emails', function( $req, $res ) { /* GET /employees/emails */ } ) ->get( '/phone-numbers', function( $req, $res ) { /* GET /employees/phone-numbers */ } ) ->post( '/children', function( $req, $res ) { /* POST /employees/children */ } ) ->end() // Finishes the group and back to "/" ->get( '/customers', function( $req, $res ) { /* GET /customers */ } ) ;
end
结束路由组并返回到父组的方法。
示例
$app-> route( '/products' ) ->get( '/colors', function( $req, $res ) { /* GET /products/colors */ } ) ->route( '/suppliers' ) ->get( '/emails', function( $req, $res ) { /* GET /products/suppliers/emails */ } ) ->end() // Finishes "/suppliers" and back to "/products" ->get( '/sizes', function( $req, $res ) { /* GET /products/sizes */ } ) ->end() // Finishes "/products" and back to "/" ->get( '/sales', function( $req, $res ) { /* GET /sales */ } ) ;
use
添加一个 中间件,在它之后声明的路由之前进行评估。
示例
$app ->use( $myMiddlewareFunction ) ->get( '/hello', $sayHelloFunction ); // Executes after the middleware
listen
执行路由器的方法。
function listen( array|RouterOptions $options = [] ): void
选项包括
rootURL
是一个字符串,用于设置根 URL。例如:dirname( $_SERVER['PHP_SELF'] )
。默认为''
。req
是实现HttpRequest
接口的对象,用于从 HTTP 请求中检索所有头和数据。更改它仅在你想要单元测试你的 API 时有用 - 查看 Mocking an HTTP request。默认情况下,它将接收来自RealHttpRequest
类的对象。res
是实现HttpResponse
接口的对象。你可能不需要更改它的值。默认情况下,它将接收来自RealHttpResponse
类的对象。
示例
// Sets the 'rootURL' to where the index.php is located. $app->listen( [ 'rootURL' => dirname( $_SERVER['PHP_SELF'] ) ] );
您还可以使用 RouterOptions
实例来设置选项
use phputil\router\RouterOptions; // Sets the 'rootURL' to where the index.php is located. $app->listen( ( new RouterOptions() )->withRootURL( dirname( $_SERVER['PHP_SELF'] ) ) );
路由器选项
withRootURL
withRootURL( string $url ): RouterOptions
withReq
withReq( HttpRequest $req ): RouterOptions
withRes
withRes( HttpResponse $res ): RouterOptions
HttpRequest
表示 HTTP 请求的接口。
API
interface HttpRequest { /** Returns the current URL or `null` on failure. */ function url(): ?string; /** Returns the current URL without any queries. E.g. `/foo?bar=10` -> `/foo` */ function urlWithoutQueries(): ?string; /** Returns the URL queries. E.g. `/foo?bar=10&zoo=A` -> `['bar'=>'10', 'zoo'=>'A']` */ function queries(): array; /** Returns all HTTP request headers */ function headers(): array; /** Returns the header with the given case-insensitive name, or `null` if not found. */ function header( $name ): ?string; /** Returns the raw body or `null` on failure. */ function rawBody(): ?string; /** * Returns the converted content, depending on the `Content-Type` header: * - For `x-www-form-urlencoded`, it returns an `array`; * - For `application/json`, it returns an `object` or an `array` (depending on the content). * - Otherwise it returns a `string`, or `null` on failure. */ function body(); /** Returns the HTTP request method or `null` on failure. */ function method(): ?string; /** Returns all cookies as an array (map). */ function cookies(): array; /** * Returns the cookie value with the given case-insensitive key or `null` if not found. * * @param string $key Cookie key. * @return string|null */ function cookie( $key ): ?string; /** * Returns a URL query or route parameter with the given name (key), * or `null` when the given name is not found. * * @param string $name Parameter name. * @return string */ function param( $name ): ?string; /** * Returns all the URL queries and route parameters as an array (map). * @return array */ function params(): array; /** * Returns extra, user-configurable data. * @return ExtraData */ function extra(): ExtraData; }
ExtraData
额外的、用户自定义的数据。
class ExtraData { /** * Sets a value to the given key. Chainable method. * * @param string|int $key * @param mixed $value * @return ExtraData */ function set( $key, $value ): ExtraData; /** * Returns the value for the given key, or null otherwise. * @param string|int $key * @return mixed */ function get( $key ); /** * Returns the keys and values as an array. */ function toArray(): array; }
HttpResponse
表示 HTTP 响应的接口。
其大多数方法是可链式的,也就是说,您可以按顺序调用它们。例如
$response->status( 201 )->send( 'Saved successfully.' );
API
interface HttpResponse { /** * Sets the HTTP status code. * * @param int $code HTTP status code. * @return HttpResponse */ function status( int $code ): HttpResponse; /** * Indicates if the current HTTP status code is equal to the given one. * * @param int $code HTTP status code. * @return bool */ function isStatus( int $code ): bool; /** * Sets an HTTP header. * * @param string $header HTTP header. * @param string|int|float|bool|array $value Header value. * @return HttpResponse */ function header( string $header, $value ): HttpResponse; /** * Indicates if the response has the given HTTP header. * * @param string $header HTTP header. * @return boolean */ function hasHeader( string $header ): bool; /** * Returns the response header, if it exists. Returns `null` otherwise. * * @param string $header HTTP header. * @return string|null */ function getHeader( string $header ): ?string; /** * Removes a header. * * @param string $header Header to remove. */ function removeHeader( string $header ): void; /** * Sets a redirect response. * * @param int $statusCode HTTP status code. * @param string|null $path Path. * @return HttpResponse */ function redirect( int $statusCode, $path = null ): HttpResponse; /** * Sets a cookie. * * @param string $name Name (key) * @param string $value Value. * @param array $options Optional map with the following options: * - `domain`: string * - `path`: string * - `httpOnly`: true|1 * - `secure`: true|1 * - `maxAge`: int * - `expires`: string * - `sameSite`: true|1 * @return HttpResponse * * @see https://mdn.org.cn/en-US/docs/Web/HTTP/Cookies for options' meanings. */ function cookie( string $name, string $value, array $options = [] ): HttpResponse; /** * Clears a cookie with the given name (key). * * @param string $name Name (key) * @param array $options Optional map with the same options as #cookie()'s. * @return HttpResponse */ function clearCookie( string $name, array $options = [] ): HttpResponse; /** * Sets the `Content-Type` header with the given MIME type. * * @param string $mime MIME type. * @return HttpResponse */ function type( string $mime ): HttpResponse; /** * Sends the given HTTP response body. * * @param mixed $body Response body. * @return HttpResponse */ function send( $body ): HttpResponse; /** * Sends a file based on its path. * * @param string $path File path * @param array $options Optional map with the options: * - `mime`: string - MIME type, such as `application/pdf`. * @return HttpResponse */ function sendFile( string $path, array $options = [] ): HttpResponse; /** * Send the given content as JSON, also setting the needed headers. * * @param mixed $body Content to send as JSON. * @return HttpResponse */ function json( $body ): HttpResponse; /** * Ends the HTTP response. * * @param bool $clear If it is desired to clear the headers and the body after sending them. It defaults to `true`. */ function end( bool $clear = true ): HttpResponse; }
模拟 HTTP 请求
👉 对 API 测试很有用
require_once 'vendor/autoload.php'; use \phputil\router\FakeHttpRequest; use \phputil\router\Router; $app = new Router(); // Set a expectation $app->get( '/foo', function( $req, $res ) { $res->send( 'Called!' ); } ); // Mock the request $fakeReq = new FakeHttpRequest(); $fakeReq->withURL( '/foo' )->withMethod( 'GET' ); // Use the mock request $app->listen( [ 'req' => $fakeReq ] ); // It will use the fake request to call "/foo"