phputil/router

类似ExpressJS的PHP路由器

v0.3.1 2024-07-10 19:44 UTC

This package is auto-updated.

Last update: 2024-09-10 20:07:32 UTC


README

Version Build License

phputil/router

类似ExpressJS的PHP路由器

  • 没有第三方依赖
  • 单元测试
  • 可模拟 - 容易为您的API创建自动化测试

👉 目前请不要在生产环境中使用 - 仅适用于玩具项目。

安装

需要PHP 7.4+

composer require phputil/router

👉 您可能还希望安装 phputil/cors

注意事项

  • 与ExpressJS不同,phputil/router需要HTTP服务器才能运行(如果请求没有被模拟)。您可以使用您选择的HTTP服务器,例如php -S localhost:80ApacheNginxhttp-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方法(GETPOSTPUTDELETEHEADOPTIONS)和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

已知中间件

ℹ 您创建了一个有用的中间件吗?打开一个问题以包括它在这里。

API

这个库的目标不是涵盖整个ExpressJS API。然而,您可以自由地为这个项目做出贡献并添加更多功能。

类型

中间件

In phputil/router,中间件是一个函数,它

  1. 在评估路由之前执行一些操作(例如,设置响应头,验证权限)。
  2. 可以停止路由器,可选地设置响应。

语法

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'] ) ) );

路由器选项

用于 Routerlisten() 方法的选项。

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"

许可证

MIT © Thiago Delgado Pinto