大卫塞萨拉诺/embryo-routing

PHP路由器PSR兼容。

1.2.0 2021-06-18 10:06 UTC

This package is auto-updated.

Last update: 2024-09-25 19:47:09 UTC


README

一个轻量级、快速且PSR兼容的PHP路由器。

功能

  • PSR(7、11、15)兼容。
  • 静态、动态和可选路由模式。
  • 支持GET、POST、PUT、PATCH、DELETE和OPTIONS请求方法。
  • 支持路由中间件。
  • 支持路由分组。
  • 支持通过容器解析。
  • 支持子文件夹。

要求

  • PHP >= 7.1
  • URL重写
  • PSR-7 http消息实现和PSR-17 http工厂实现(例如:Embryo-Http
  • PSR-11容器实现(例如:Embryo-Container
  • PSR-15 http服务器请求处理器实现(例如:Embryo-Middleware

安装

使用Composer

$ composer require davidecesarano/embryo-routing

示例

在定义应用程序路由之前,需要创建以下对象

  • Container
  • ServerRequestFactory
  • ResponseFactory
  • RequestHandler
  • Emitter
use Embryo\Container\Container;
use Embryo\Http\Emitter\Emitter;
use Embryo\Http\Factory\{ServerRequestFactory, ResponseFactory};
use Embryo\Http\Server\RequestHandler;
use Embryo\Routing\Router;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

$container      = new Container;
$request        = (new ServerRequestFactory)->createServerRequestFromServer();
$response       = (new ResponseFactory)->createResponse(200);
$requestHandler = new RequestHandler;
$emitter        = new Emitter;

稍后,可以使用Router对象定义路由

$router = new Router;

$router->get('/', function(Request $request, Response $response){
    $response->getBody()->write('Hello World!');
    return $response;
});

现在,创建PSR-15中间件队列,添加所需的路由中间件

  • MethodOverrideMiddleware用于重写HTTP请求方法。
  • RoutingMiddleware用于匹配路由和处理器的发现。
  • RequestHandlerMiddleware用于执行路由器发现的请求处理器。
$requestHandler->add(new Embryo\Routing\Middleware\MethodOverrideMiddleware);
$requestHandler->add(new Embryo\Routing\Middleware\RoutingMiddleware($router));
$requestHandler->add(new Embryo\Routing\Middleware\RequestHandlerMiddleware($container));
$response = $requestHandler->dispatch($request, $response);

最后,可以使用Emitter对象生成响应的输出。

$emitter->emit($response);

用法

创建路由

您可以使用Router对象上的方法定义应用程序路由。每个方法接受两个参数

  • 路由模式(带有可选的占位符)
  • 路由回调(一个闭包,一个class@method字符串或一个['class', 'method']数组)
// GET Route
$router->get('/blog/{id}', function(Request $request, Response $response, int $id) {
    $response->getBody()->write('This is post with id '.$id);
    return $response;
});

请注意,您可以使用以下方式使用“/”或不使用“/”开头来编写模式:blog/{id}

方法

Embryo Routing支持GET、POST、PUT、PATCH、DELETE和OPTIONS请求方法。每个请求方法都对应于Router对象的一个方法:get()post()put()patch()delete()options()。您可以使用all()map()方法来支持所有方法或特定路由方法。

// All methods
$router->all('pattern', function(Request $request, Response $response) {
    //...
});

// Match methods
$router->map(['GET', 'POST'], 'pattern', function(Request $request, Response $response) {
    //...
});

重写请求方法

使用X-HTTP-Method-Override来重写HTTP请求方法。仅当原始请求方法为POST时才有效。X-HTTP-Method-Override的允许值为PUTDELETEPATCH。Embryo使用MethodOverrideMiddleware来管理HTTP-Method-Override。

回调函数

每个路由方法都接受一个回调函数作为其最后一个参数。默认情况下,此参数至少接受两个参数

  • 请求。第一个参数是代表当前HTTP请求的Psr\Http\Message\ServerRequestInterface对象。
  • 响应。第二个参数是代表当前HTTP响应的Psr\Http\Message\ResponseInterface对象。
  • 占位符。每个路由占位符都有一个单独的参数。

向响应写入内容

有三种方法可以将内容写入HTTP响应

  1. 您可以直接在路由回调中 echo() 内容:这些内容将被附加到当前的HTTP响应对象中。
  2. 您可以返回一个 Psr\Http\Message\ResponseInterface 对象。
  3. 当返回数组时,您可以返回 json 内容。
// echo
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    echo $name;
});

// response object
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    $response->getBody()->write($name);
    return $response;
});

// json
$router->get('/hello/{name}', function ($Request $request, Response $response, string $name) {
    return [
        'name' => $name
    ];
});

闭包绑定

如果您使用闭包实例作为路由回调,闭包的状态将被绑定到 Container 实例。这意味着您可以通过 $this 关键字在闭包内部访问DI容器实例。

$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    $myservice = $this->get('myservice');
    //...
});

访问当前路由

如果您获取路由的对象,您可以使用请求属性来使用它。

$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    $route = $request->getAttribute('route');
    echo $route->getUri(); // /hello/name
});

占位符

路由模式可以使用命名占位符来动态匹配HTTP请求URI段。

格式

路由模式占位符以 { 开始,后跟占位符名称,以 } 结束。名称和值占位符可以是字母、数字,包括下划线(_)。

$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    echo $name;
});

可选

要使占位符可选,请将其包裹在方括号中

$router->get('/hello[/{name}]', function (Request $request, Response $response, string $name = null) {
    if ($name) {
        echo $name;
    }
    //...
});

您可以使用多个可选参数

$router->get('/blog[/{year}][/{month}][/{day}]', function(Request $request, Response $response, int $year = 2018, int $month = 12, int $day = 31){
    $response->getBody()->write('Blog! Year: '.$year.', Month: '.$month.', Day: '.$day);
    return $response;
});

对于“简单缩写”可选参数,您可以这样做

$router->get('/blog[/{year}][/{slug}]', function(Request $request, Response $response, int $year = 2018, string $slug = null){
    //...
})->where('slug', '[\/\w\-]+');

在这个例子中,URI为/blog/2018/my-post-title将导致$year(2018)和$slug(my-post-title)参数。

设置正则表达式路由

默认情况下,占位符可以接受用于组成URI的任何字符,除了 / 字符。然而,占位符也可以要求HTTP请求URI与特定的正则表达式匹配。为此,您可以使用 where() 方法

$router->get('/blog/{id}/{name}', function(Request $request, Response $response, int $id, string $name){
    //...
})->where([
    'id'   => '[0-9]+',
    'name' => '[a-z]+',
]);

设置路由名称

您可以使用 name() 方法在路由中指定名称

$router->get('/hello/{name}', function (Request $request, Response $response, string $name) {
    //...
})->name('route');

创建路由分组

您可以使用 group() 方法将路由组织成逻辑组。如果您想添加路由前缀,可以使用 prefix() 方法

$router->prefix('/api')->group(function($router) {
    $router->get('/user/{id}', function(Request $request, Response $response, int $id) {
        //...
    });
});

在这个例子中,URI是例如 /api/user/1。

向路由添加中间件

您也可以将PSR-15中间件附加到任何路由或路由组。 middleware() 方法接受字符串、数组或 Psr\Http\Server\MiddlewareInterface 实例。

路由中间件

您可以使用 middleware() 方法在路由上分配一个或多个中间件

$router->get('/users', function(Request $request, Response $response) {
    //...
})->middleware('App\TestMiddleware1', 'App\TestMiddleware2');

组中间件

除了路由之外,您还可以将一个或多个中间件分配给一个组以及组内的单个路由

$router->prefix('/api')->middleware('App\GroupMiddlewareTest')->group(function($router) {
    $router->get('/user/{id}', function(Request $request, Response $response, int $id){
        //...
    })->middleware('App\RouteMiddlewareTest');
});

通过容器解析

在回调中,除了闭包之外,您还可以使用 class@method 字符串或 ['className', 'methodName'] 数组

// string
$router->get('/user/{id}', 'user@getById');

// array
use Path\To\User;

$router->get('/user/{id}', [User::class, 'geById']);

它转换为一个类似这样的类/方法调用

use Embryo\Routing\Controller;
use Psr\Http\Message\ResponseInterface;

class User extends Controller
{
    public function getById(int $id): ResponseInterface
    {
        $this->response->getBody()->write('The User id is: '.$id);
        return $this->response;
    }
}

在这个例子中,您将可以通过 $this 关键字在类内部访问DI容器实例。如果您想访问容器中的服务,请使用 $this->get('myservice')。如果您想访问请求或响应实例,请使用 $this->request$this->response。此外,您还可以使用 type-hinting 来调用方法参数,这意味着您可以在方法中放置所需的类,服务容器将自动解决它

use Path\To\MyService;
use Embryo\Routing\Controller;
use Psr\Http\Message\ResponseInterface;

class User extends Controller
{
    public function getById(MyService $service, int $id): ResponseInterface
    {
        //...
    }
}

设置默认命名空间

您可以使用 setNamespace() 方法设置您控制器的默认命名空间

$router = new Router;
$router->setNamespace('App\\Controller');

$router->get('/', 'PageController@index'); // App\Controller\PageController

//...

在子文件夹中工作

Embryo Routing可以通过设置路径使用 setBasePath() 方法在子目录中工作

$router = new Router;
$router->setBasePath('/path/subdirectory');

$router->get('/', function(Request $request, Response $response) {
    $response->getBody()->write('Hello World!');
    return $response;
});

//...