fizk/ router
一个非常简单的PSR-7路由器
Requires
- php: >=8.1
- psr/http-message: ^1.0
Requires (Dev)
- laminas/laminas-diactoros: ^2.8
- phpunit/phpunit: ^9.5
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
数组键将成为路由的名称。必需的 pattern
和 options
键将传递给路由实例。可以定义可选的 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\Router
与 Psr\Http\Message\ResponseInterface
和 Psr\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); }