fizkrouter

一个非常简单的PSR-7路由器

2.1.0 2023-02-16 05:49 UTC

This package is auto-updated.

Last update: 2024-09-16 08:56:37 UTC


README

一个非常简单的PSR-7兼容路由器。

它如何工作。

这个路由器并不试图变得聪明或巧妙。它对要路由的内容没有意见。它不会将路由表达式重写为正则表达式,而是希望从一开始就将表达式用正则表达式表达出来。这允许对与URI匹配的内容有更大的控制。

这个路由器是构建为一个树。这使得路由器稍微快一点,因为它不需要遍历整个列表(和每个路由定义)来找到匹配项。

2 3 4 1

如何使用它。

定义一个路由,给它一个名字,一个表达式,以及当路由匹配时你想要返回的参数,然后将其与一个 PSR-7请求 匹配

use Fizk\Router\Route;
use Laminas\Diactoros\Request;

$routes = new Route(
    'root',
    '/path/(?<id>\d+)',
    ['handler' => SomeHandler::class]
);

$request = new Request('http://this.is/path/1');

$match = $routes->match($request);

print_r($match->getAttributes())
// Will print
//[
//  'id' => '1'
//]

print_r($match->getParams())
// Will print
//[
//  'handler' => 'Namespace\\SomeHandler'
//]

如你所见,没有那种 /path/:id 语法,相反,你需要写出完整的表达式。如果你想捕获URI中的属性,你必须通过使用 命名捕获 给它们命名

嵌套路由。

假设我们有一个根路径:/path,然后我们可以有数字或字母作为属性,我们想要根据提供的属性类型运行不同的处理器/控制器。我们可以这样表达

use Fizk\Router\Route;
use Laminas\Diactoros\Request;

$routes = (new Route('path', '/path', []))
    ->addRoute(new Route('letters', '/(?<id>[a-z]+)', ['controller' => SomeLetterController::class]))
    ->addRoute(new Route('number', '/(?<slug>\d+)', ['controller' => SomeNumberController::class]))
    ;

echo $routes->match(new Request('http://this.is/path/1'))->getParam('handler');
// Will print
// Namespace\\SomeNumberController

echo $routes->match(new Request('http://this.is/path/arg'))->getParam('handler');
// Will print
// Namespace\\SomeLetterController

路由可以被嵌套“无限”深。

路由器类。

使用 ->addRoute(...) 语法定义路由可能有点啰嗦。这个库提供了一个类,它可以接受数组形式的配置并构建路由树,这样路由配置就更容易管理了。

// router.config.php
return [
    'base' => [
        'pattern' => '/',
        'options' => ['handler' => 'IndexHandler'],
    ],
    'albums' => [
        'pattern' => '/albums',
        'options' => ['handler' => 'AlbumsHandler'],
        'children' => [
            'album' => [
                'pattern' => '/(?<id>\d+)',
                'options' => ['handler' => 'AlbumHandler'],
            ],
        ]
    ],
];
// index.php

$router = new Router(require './router.config.php');

$request = new Request('http://example.com/albums/1');
echo $router->match($request)->getParams('handler');

// Will print
// AlbumHandler

数组键将成为路由的名称。必需的 patternoptions 键将传递给路由实例。可以定义可选的 children 键,这些路由将成为父路由的子路由。

因为这个类所有配置都在内部,它可以提供一个名为 public function construct(string $path, ?array $arguments = []): ?string; 的方法。它可以基于你给路由命名的名称构建URI。一个例子是这样的

$config = [
    'index' => [
        'pattern' => '/',
        'options' => ['handler' => 'IndexHandler'],
    ],
    'albums' => [
        'pattern' => '/albums',
        'options' => ['handler' => 'AlbumsHandler'],
        'children' => [
            'album' => [
                'pattern' => '/(?<id>\d+)',
                'options' => ['handler' => 'AlbumHandler'],
            ],
        ]
    ],
];

$router = new Router($config);
echo $router->construct('albums/album', ['id' => 1]);

// This will print
//  /albums/1

示例

这个例子使用了 Fizk\RouterPsr\Http\Message\ResponseInterfacePsr\Http\Message\ServerRequestInterface 结合使用。重要的是要理解路由器不会从URI中注入任何值到 $responce 对象或调用控制器/处理器。这些都是你必须自己处理的事情。

这种做法的好处是,路由器不依赖于控制器/处理器的实现方式或它使用的PSR标准。

use Fizk\Router\Route;
use Laminas\Diactoros\Request;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

//Define Handlers/Controllers
class SomeNumberController implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $id = $request->getAttribute('id');
        $data = $service->getById($id);
        return new JsonResponse($data);
    }
}

class SomeLetterController implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $slug = $request->getAttribute('slug');
        $data = $service->getBySlug($slug);
        return new JsonResponse($slug);
    }
}

class ResourceNotFoundController implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new JsonResponse(['message' => 'Resource Not Found'], 404);
    }
}

// Create a Request Object, pulling CGI values from global scope
$request = ServerRequestFactory::fromGlobals(
    $_SERVER,
    $_GET,
    $_POST
    $_COOKIE,
    $_FILES
);
// Define an Emitter, which will set HTTP headers and body before delivering to client
$emitter = new SapiEmitter();

// Define Routes
$routes = (new Route('path', '/path', []))
    ->addRoute(new Route('letters', '/(?<id>[a-z]+)', ['controller' => new SomeLetterController()]))
    ->addRoute(new Route('number', '/(?<slug>\d+)', ['controller' => new SomeNumberController]))
    ;

// ...OR USE THE MORE COMPACT WAY OF DEFINING ROUTES
$routes = new Router([
    'path' => [
        'pattern' => '/path',
        'options' => []
        'children' => [
            'letters' => [
                'pattern' => '/(?<id>[a-z]+)',
                'options' => ['controller' => new SomeLetterController()]
            ],
            'numbers' => [
                'pattern' => '/(?<slug>\d+)',
                'options' => ['controller' => new SomeNumberController()]
            ],
        ]
    ]
]);

// Match Routes against Request Object
$match = $routes->match($request);

if ($match) {
    //Add attributes from URI to the Request Object
    foreach ($match->getAttributes() as $name => $value) {
        $request = $request->withAttribute($name, $value);
    }

    // Run the Handler/Controller
    $response = $match->getParam('controller')->handle($request);

    // Emit a Response back to client
    $emitter->emit($response);
} else {
    // Run the Error Handler/Controller
    $response = (new ResourceNotFoundController())->handle($request);

    // Emit a Response back to client
    $emitter->emit($response);
}