sunrise / http-router
基于PSR-7和PSR-15的PHP 7.1+ HTTP路由器,支持注解/属性和OpenAPI(Swagger)规范
v3.0.0-beta.9
2024-07-15 20:49 UTC
Requires
- php: >=8.1
- fig/http-message-util: ^1.1
- psr/container: ^1.0 || ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- doctrine/dbal: ^3.7
- doctrine/orm: ^2.16
- doctrine/persistence: ^3.2
- filp/whoops: ^2.15
- monolog/monolog: ^2.9
- neomerx/cors-psr7: ^3.0
- php-di/php-di: ^7.0
- phpunit/phpunit: ^9.6
- ramsey/uuid: ^4.7
- sunrise/coding-standard: ^1.0
- sunrise/http-message: ^3.0
- sunrise/hydrator: ^3.7
- symfony/console: ^6.0
- symfony/event-dispatcher: ^6.0
- symfony/property-access: ^6.0
- symfony/serializer: ^6.0
- symfony/uid: ^6.0
- symfony/validator: ^6.0
- twig/twig: ^3.7
- vimeo/psalm: ^5.15
- dev-master
- v3.0.0-beta.9
- v3.0.0-beta.8
- v3.0.0-beta.7
- v3.0.0-beta.6
- v3.0.0-beta.5
- v3.0.0-beta.4
- v3.0.0-beta.3
- v3.0.0-beta.2
- v3.0.0-beta.1
- v2.16.2
- v2.16.1
- v2.16.0
- v2.15.1
- v2.15.0
- v2.14.1
- v2.14.0
- v2.13.0
- v2.12.0
- v2.11.2
- v2.11.1
- v2.11.0
- v2.10.2
- v2.10.1
- v2.10.0
- v2.9.0
- v2.8.0
- v2.7.0
- v2.6.0
- v2.5.0
- v2.4.2
- v2.4.1
- v2.4.0
- v2.3.0
- v2.2.0
- v2.1.0
- v2.0.2
- v2.0.1
- v2.0.0
- 1.x-dev
- v1.0.14
- v1.0.13
- v1.0.12
- v1.0.11
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- dev-renovate/phpunit-phpunit-11.x
- dev-renovate/phpunit-phpunit-9.x
- dev-release/v3.0.0
- dev-renovate/major-symfony
- dev-renovate/cimg-php-8.x
- dev-renovate/psr-http-message-2.x
- dev-renovate/psr-simple-cache-3.x
- dev-renovate/doctrine-annotations-2.x
- dev-renovate/cimg-php-7.x
- dev-renovate/sunrise-http-factory-2.x
This package is auto-updated.
Last update: 2024-09-19 14:24:42 UTC
README
psr路由器,支持注解的路由器,支持属性的路由器,PHP路由器。
安装
composer require 'sunrise/http-router:^2.15'
对OpenAPI(Swagger)规范的支持(可选)
composer require 'sunrise/http-router-openapi:^2.0'
更多详细信息请见此处: sunrise/http-router-openapi。
快速入门
此示例使用其他sunrise包,但您可以使用例如 zend/diactoros
或任何其他。
composer require sunrise/http-message sunrise/http-server-request
use Sunrise\Http\Message\ResponseFactory; use Sunrise\Http\Router\RouteCollector; use Sunrise\Http\Router\Router; use Sunrise\Http\ServerRequest\ServerRequestFactory; use function Sunrise\Http\Router\emit; $collector = new RouteCollector(); // PSR-15 request handler (optimal performance): $collector->get('home', '/', new HomeRequestHandler()); // or you can use an anonymous function as your request handler: $collector->get('home', '/', function ($request) { return (new ResponseFactory)->createResponse(200); }); // or you can use the name of a class that implements PSR-15: $collector->get('home', '/', HomeRequestHandler::class); // or you can use a class method name as your request handler: // (note that such a class mayn't implement PSR-15) $collector->get('home', '/', [HomeRequestHandler::class, 'index']); // most likely you will need to use PSR-11 container: // (note that only named classes will be pulled from such a container) $collector->setContainer($container); $router = new Router(); $router->addRoute(...$collector->getCollection()->all()); $request = ServerRequestFactory::fromGlobals(); $response = $router->handle($request); emit($response);
使用示例
研究sunrise/awesome-skeleton以了解如何使用此功能。
从配置加载路由的策略
请注意,自版本2.10.0起必须使用类
ConfigLoader
。
use Sunrise\Http\Router\Loader\ConfigLoader; use Sunrise\Http\Router\Router; $loader = new ConfigLoader(); // set container if necessary... $loader->setContainer($container); // attach configs... $loader->attach('routes/api.php'); $loader->attach('routes/admin.php'); $loader->attach('routes/public.php'); // or attach a directory... // [!] available from version 2.2 $loader->attach('routes'); // or attach an array... // [!] available from version 2.4 $loader->attachArray([ 'routes/api.php', 'routes/admin.php', 'routes/public.php', ]); // install container if necessary... $loader->setContainer($container); $router = new Router(); $router->load($loader); // if the router matching should be isolated for top middlewares... // for example for error handling... // [!] available from version 2.8 $response = $router->run($request); // if the router is used as a request handler $response = $router->handle($request); // if the router is used as middleware $response = $router->process($request, $handler);
/** @var Sunrise\Http\Router\RouteCollector $this */ $this->get('home', '/', new CallableRequestHandler(function ($request) { return (new ResponseFactory)->createJsonResponse(200); })); // or using a direct reference to a request handler... $this->get('home', '/', new App\Http\Controller\HomeController());
请注意,自版本2.10.0起,您可以通过不同的方式引用请求处理器。
/** @var Sunrise\Http\Router\RouteCollector $this */ $this->get('home', '/', function ($request) { return (new ResponseFactory)->createJsonResponse(200); }); $this->get('home', '/', App\Http\Controller\HomeController::class, [ App\Http\Middleware\FooMiddleware::class, App\Http\Middleware\BarMiddleware::class, ]); $this->get('home', '/', [App\Http\Controller\HomeController::class, 'index'], [ App\Http\Middleware\FooMiddleware::class, App\Http\Middleware\BarMiddleware::class, ]);
从描述符(注解或属性)加载路由的策略
如果您将使用注解,请安装doctrine/annotations包
composer require doctrine/annotations
请注意,自版本2.10.0起必须使用类
DescriptorLoader
。
请注意,自版本2.10.0起,您可以将@Rote()注解绑定到类方法上。
use Doctrine\Common\Annotations\AnnotationRegistry; use Sunrise\Http\Router\Loader\DescriptorLoader; use Sunrise\Http\Router\Router; // necessary if you will use annotations (annotations isn't attributes)... AnnotationRegistry::registerLoader('class_exists'); $loader = new DescriptorLoader(); // set container if necessary... $loader->setContainer($container); // attach a directory with controllers... $loader->attach('src/Controller'); // or attach an array // [!] available from version 2.4 $loader->attachArray([ 'src/Controller', 'src/Bundle/BundleName/Controller', ]); // or attach a class only // [!] available from 2.10 version. $loader->attach(App\Http\Controller\FooController::class); $router = new Router(); $router->load($loader); // if the router matching should be isolated for top middlewares... // for example for error handling... // [!] available from version 2.8 $response = $router->run($request); // if the router is used as a request handler $response = $router->handle($request); // if the router is used as middleware $response = $router->process($request, $handler);
use Sunrise\Http\Router\Annotation as Mapping; #[Mapping\Prefix('/api/v1')] #[Mapping\Middleware(SomeMiddleware::class)] class SomeController { #[Mapping\Route('foo', path: '/foo')] public function foo() { // will be available at: /api/v1/foo } #[Mapping\Route('bar', path: '/bar')] public function bar() { // will be available at: /api/v1/bar } }
无加载策略
use App\Controller\HomeController; use Sunrise\Http\Router\RouteCollector; use Sunrise\Http\Router\Router; $collector = new RouteCollector(); // set container if necessary... $collector->setContainer($container); $collector->get('home', '/', new HomeController()); $router = new Router(); $router->addRoute(...$collector->getCollection()->all()); // if the router matching should be isolated for top middlewares... // for example for error handling... // [!] available from version 2.8 $response = $router->run($request); // if the router is used as a request handler $response = $router->handle($request); // if the router is used as middleware $response = $router->process($request, $handler);
错误处理示例
use Sunrise\Http\Message\ResponseFactory; use Sunrise\Http\Router\Exception\MethodNotAllowedException; use Sunrise\Http\Router\Exception\RouteNotFoundException; use Sunrise\Http\Router\Middleware\CallableMiddleware; use Sunrise\Http\Router\RequestHandler\CallableRequestHandler; use Sunrise\Http\Router\RouteCollector; use Sunrise\Http\Router\Router; use Sunrise\Http\ServerRequest\ServerRequestFactory; use function Sunrise\Http\Router\emit; $collector = new RouteCollector(); $collector->get('home', '/', new CallableRequestHandler(function ($request) { return (new ResponseFactory)->createJsonResponse(200); })); $router = new Router(); $router->addRoute(...$collector->getCollection()->all()); $router->addMiddleware(new CallableMiddleware(function ($request, $handler) { try { return $handler->handle($request); } catch (MethodNotAllowedException $e) { return (new ResponseFactory)->createResponse(405); } catch (RouteNotFoundException $e) { return (new ResponseFactory)->createResponse(404); } catch (Throwable $e) { return (new ResponseFactory)->createResponse(500); } })); emit($router->run(ServerRequestFactory::fromGlobals()));
与PSR-11容器一起工作
收集器
$collector = new RouteCollector(); /** @var \Psr\Container\ContainerInterface $container */ // Pass DI container to the collector... $collector->setContainer($container); // Objects passed as strings will be initialized through the DI container... $route = $collector->get('home', '/', HomeController::class, [ FooMiddleware::class, BarMiddleware::class, ]);
配置加载器
$loader = new ConfigLoader(); /** @var \Psr\Container\ContainerInterface $container */ // Pass DI container to the loader... $loader->setContainer($container); // All found objects which has been passed as strings will be initialized through the DI container... $routes = $loader->load();
描述符加载器
$loader = new DescriptorLoader(); /** @var \Psr\Container\ContainerInterface $container */ // Pass DI container to the loader... $loader->setContainer($container); // All found objects will be initialized through the DI container... $routes = $loader->load();
描述符缓存(PSR-16)
$loader = new DescriptorLoader(); /** @var \Psr\SimpleCache\CacheInterface $cache */ // Pass a cache to the loader... $loader->setCache($cache);
路由注解示例
最小注解视图
/** * @Route( * name="api_v1_entry_update", * path="/api/v1/entry/{id<@uuid>}(/{optionalAttribute})", * methods={"PATCH"}, * ) */ final class EntryUpdateRequestHandler implements RequestHandlerInterface
完整注解
/** * @Route( * name="api_v1_entry_update", * host="api.host", * path="/api/v1/entry/{id<@uuid>}(/{optionalAttribute})", * methods={"PATCH"}, * middlewares={ * "App\Middleware\CorsMiddleware", * "App\Middleware\ApiAuthMiddleware", * }, * attributes={ * "optionalAttribute": "defaultValue", * }, * summary="Updates an entry by UUID", * description="Here you can describe the method in more detail...", * tags={"api", "entry"}, * priority=0, * ) */ final class EntryUpdateRequestHandler implements RequestHandlerInterface
仅一个方法
/** * @Route( * name="home", * path="/", * method="GET", * ) */
路由属性示例
最小属性视图
use Sunrise\Http\Router\Annotation\Route; #[Route( name: 'api_v1_entry_update', path: '/api/v1/entry/{id<@uuid>}(/{optionalAttribute})', methods: ['PATCH'], )] final class EntryUpdateRequestHandler implements RequestHandlerInterface
完整属性
use Sunrise\Http\Router\Annotation\Route; #[Route( name: 'api_v1_entry_update', host: 'api.host', path: '/api/v1/entry/{id<@uuid>}(/{optionalAttribute})', methods: ['PATCH'], middlewares: [ \App\Middleware\CorsMiddleware::class, \App\Middleware\ApiAuthMiddleware::class, ], attributes: [ 'optionalAttribute' => 'defaultValue', ], summary: 'Updates an entry by UUID', description: 'Here you can describe the method in more detail...', tags: ['api', 'entry'], priority: 0, )] final class EntryUpdateRequestHandler implements RequestHandlerInterface
其他注解
use Sunrise\Http\Router\Annotation\Host; #[Host('admin')] #[Prefix('/api/v1')] #[Postfix('.json')] #[Middleware(SomeMiddleware::class)] final class SomeController { #[Route('foo', '/foo')] public function foo(ServerRequestInterface $request) : ResponseInterface { // this action will be available at: // http://admin.host/api/v1/foo.json // // this can be handy to reduce code duplication... } }
有用的信息
JSON-payload解码
use Sunrise\Http\Router\Middleware\JsonPayloadDecodingMiddleware; $router->addMiddleware(new JsonPayloadDecodingMiddleware());
通过名称获取路由
// checks if a route is exists $router->hasRoute('foo'); // gets a route by name $router->getRoute('foo');
获取当前路由
通过路由器
从版本2.12可用
$router->getMatchedRoute();
通过请求
从版本1.x可用,但之前没有记录...
$request->getAttribute('@route'); // or $request->getAttribute(\Sunrise\Http\Router\RouteInterface::ATTR_ROUTE);
通过事件
从版本2.13可用
$eventDispatcher->addListener(RouteEvent::NAME, function (RouteEvent $event) { $event->getRoute(); });
生成路由URI
$uri = $router->generateUri('route.name', [ 'attribute' => 'value', ], true);
运行路由
$response = $router->getRoute('route.name')->handle($request);
路由分组
注解示例在此处。
$collector->group(function ($collector) { $collector->group(function ($collector) { $collector->group(function ($collector) { $collector->get('api.entry.read', '/{id<\d+>}', ...) ->addMiddleware(...); // add the middleware(s) to the route... }) ->addPrefix('/entry') // add the prefix to the group... ->prependMiddleware(...); // add the middleware(s) to the group... }, [ App\Http\Middleware\Bar::class, // resolvable middlewares... ]) ->addPrefix('/v1') // add the prefix to the group... ->prependMiddleware(...); // add the middleware(s) to the group... }, [ App\Http\Middleware\Foo::class, // resolvable middlewares... ]) ->addPrefix('/api') // add the prefix to the group... ->prependMiddleware(...); // add the middleware(s) to the group...
路由模式
$collector->get('api.entry.read', '/api/v1/entry/{id<\d+>}(/{optional<\w+>})');
全局路由模式
// @uuid pattern $collector->get('api.entry.read', '/api/v1/entry/{id<@uuid>}'); // @slug pattern $collector->get('api.entry.read', '/api/v1/entry/{slug<@slug>}'); // Custom patterns (available from version 2.9.0): \Sunrise\Http\Router\Router::$patterns['@id'] = '[1-9][0-9]*'; // Just use the custom pattern... $collector->get('api.entry.read', '/api/v1/entry/{id<@id>}');
最好通过路由器设置模式
// available since version 2.11.0 $router->addPatterns([ '@id' => '[1-9][0-9]*', ]);
...或通过路由器构建器
// available since version 2.11.0 $builder->setPatterns([ '@id' => '[1-9][0-9]*', ]);
主机(从版本2.6.0可用)
注意:如果您没有为路由分配主机,它将在任何主机上可用!
// move the hosts table into the settings... $router->addHost('public.host', 'www.example.com', ...); $router->addHost('admin.host', 'secret.example.com', ...); $router->addHost('api.host', 'api.example.com', ...); // ...or: $router->addHosts([ 'public.host' => ['www.example.com', ...], ... ]); // the route will available only on the `secret.example.com` host... $route->setHost('admin.host'); // routes in the group will available on the `secret.example.com` host... $collector->group(function ($collector) { // some code... }) ->setHost('admin.host');
您可以从版本2.14.0开始按如下方式解析主机名
$router->addHost('admin', 'www1.admin.example.com', 'www2.admin.example.com'); $router->resolveHostname('www1.admin.example.com'); // return "admin" $router->resolveHostname('www2.admin.example.com'); // return "admin" $router->resolveHostname('unknown'); // return null
您还可以通过主机名获取所有路由
$router->getRoutesByHostname('www1.admin.example.com');
路由持有者
$route->getHolder(); // return Reflector (class, method or function)
路由器构建器
$router = (new RouterBuilder) ->setEventDispatcher(...) // null or use to symfony/event-dispatcher... ->setContainer(...) // null or PSR-11 container instance... ->setCache(...) // null or PSR-16 cache instance... (only for descriptor loader) ->setCacheKey(...) // null or string... (only for descriptor loader) ->useConfigLoader([]) // array with files or directory with files... ->useDescriptorLoader([]) // array with classes or directory with classes... ->setHosts([]) // ->setMiddlewares([]) // array with middlewares... ->setPatterns([]) // available since version 2.11.0 ->build();
CLI命令
use Sunrise\Http\Router\Command\RouteListCommand; new RouteListCommand($router);
事件
从版本2.13可用
composer require symfony/event-dispatcher
use Sunrise\Http\Router\Event\RouteEvent; use Symfony\Component\EventDispatcher\EventDispatcher; $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener(RouteEvent::NAME, function (RouteEvent $event) { // gets the matched route: $event->getRoute(); // gets the current request: $event->getRequest(); // overrides the current request: $event->setRequest(ServerRequestInterface $request); }); $router->setEventDispatcher($eventDispatcher);
测试运行
composer test