hotaruma / http-router
简单的HTTP路由器
Requires
- php: >=8.1
Requires (Dev)
- phpbench/phpbench: ^1.2
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10
- squizlabs/php_codesniffer: ^3.7
README
简单的HTTP路由器。
导航
安装
您可以使用Composer安装此库。运行以下命令
composer require hotaruma/http-router
路由图
创建路由
要创建路由,您需要使用RouteMap
类。以下是一个创建路由的示例
use Hotaruma\HttpRouter\RouteMap; $routeMap = new RouteMap(); $routeMap->get('/hello', function () { echo 'Hello, world!'; }); $routeMap->post('/users', UserController::class);
路由参数
您也可以在路由路径中使用花括号{}
定义路由参数
$routeMap->get('/shop/{category}', CategoryController::class);
路由配置
可以通过定义默认值、规则等来配置路由。
- 默认值:当通过名称生成路由时,如果路由中没有相同名称的属性,则设置占位符的值。在这种情况下,属性的值具有优先级。
- 规则:这些在
{}
占位符内的模式验证中起作用。它们根据正则表达式或Closure
确定参数是否匹配特定类型。规则还在生成路由时用于验证参数。 - 中间件:中间件函数可以分组并以其原始形式返回。
- 名称:用于稍后生成路由。
- 方法:方法定义路由是否匹配当前请求。
$routeMap->add('/news/{category:[A-Za-z]+}/{id}', NewsController::class)->config( defaults: ['category' => 'archive', 'id' => '1'], rules: ['id' => '\d+'], middlewares: [LogMiddleware::class], name: 'newsPage', methods: [AdditionalMethod::ANY], );
建议使用命名属性进行配置。通过使用命名属性,您可以明确指定每个配置选项的目的,从而提高代码的可读性。
模式注册表
模式可以在rules
数组中定义,也可以直接在路由路径声明中定义。当在rules
数组中定义模式时,它们具有优先级。模式用花括号{}
括起来,其格式为{占位符:规则}
。以下是一个示例
$routeMap->group( rules: ['id' => 'int'], group: function (RouteMapInterface $routeMap) { $routeMap->get('/users/{id}', UserController::class . '@show'); $routeMap->get('/post/{category:slug}', PostController::class . '@show'); } );
在这个例子中,rules
数组定义了模式id:\d+
,而{category:slug}
占位符指定URL中的category
参数应为[A-Za-z0-9-_]+
。
默认情况下,我们有以下规则
$patterns = [ 'int' => '\d+', 'alpha' => '[A-Za-z]+', 'alnum' => '[A-Za-z0-9]+', 'slug' => '[A-Za-z0-9-_]+', 'uuid' => '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}', ]
您也可以定义自己的模式并将它们注册在模式注册表中。注册表中的模式可以是正则表达式字符串或执行自定义验证的Closure
。以下是一个示例
use \Hotaruma\HttpRouter\PatternRegistry\PatternRegistry; use \Hotaruma\HttpRouter\RouteDispatcher; $routeDispatcher = new RouteDispatcher(); $patternRegistry = new PatternRegistry(); $routeMap = new RouteMap(); $routeMap->get('/category/{tag:custom}/', stdClass::class); $patternRegistry->addPattern('custom', '\w{3}'); $routeDispatcher->config( requestHttpMethod: HttpMethod::tryFrom($serverRequest->getMethod()), requestPath: $serverRequest->getUri()->getPath(), routes: $routeMap->getRoutes(), patternRegistry: $patternRegistry ); $route = $routeDispatcher->match();
在这个例子中,我们使用Closure
执行验证,注册了一个名为'custom'的自定义模式。
$patternRegistry->addPattern('custom', static function (string $value, PatternRegistryInterface $patternRegistry): bool { return is_numeric($value); });
如果路由在路由路径和路由配置中指定了规则,则配置中的规则具有优先级。
分组路由
您可以使用公共前缀对路由进行分组,并应用共享中间件或其他配置
$routeMap->group( rules: ['slug' => '\w+', 'id' => '\d+'], namePrefix: 'question', methods: [HttpMethod::GET], group: function (RouteMapInterface $routeMap) { $routeMap->add('/questions/{slug}', [QuestionController::class, 'view']); $routeMap->add('/users/{id}', [UserController::class, 'view']); $routeMap->group( namePrefix: 'admin', pathPrefix: 'admin', middlewares: [LogMiddleware::class, AccessMiddleware::class], methods: [HttpMethod::DELETE, HttpMethod::POST], group: function (RouteMapInterface $routeMap) { $routeMap->add('/questions/{id}', AdminQuestionController::class); $routeMap->add('/users/{id}', AdminUserController::class); } ); } );
当分组路由并嵌套一个组在另一个组内时,您具有合并配置的能力。这意味着组内的每个路由都会将其配置与组配置合并,每个嵌套组也会将其配置与其父组合并。
通过将路由组织到组中,您可以一次对多个路由应用特定配置。配置按顺序向下传递到嵌套组,允许您按需继承和覆盖设置。这提供了一种强大且灵活的方式来管理和组织您的路由。
$routeMap->group( pathPrefix: 'admin', methods: [HttpMethod::GET], middlewares: [ManagerAccessMiddleware::class], group: function (RouteMapInterface $routeMap) { $routeMap->add('/dashboard', [AdminController::class, 'dashboard']); $routeMap->changeGroupConfig( middlewares: [AdminAccessMiddleware::class], methods: [HttpMethod::GET, HttpMethod::DELETE, HttpMethod::POST], ); $routeMap->add('/users/{id}', [AdminController::class, 'users']); $routeMap->add('/settings', [AdminController::class, 'settings']); } );
路由扫描器
通过使用PHP 8的属性语法,您可以轻松地将路由属性注释到类和方法中,简化了在应用程序中定义路由的过程。
use Hotaruma\HttpRouter\Attribute\{Route, RouteGroup}; use Hotaruma\HttpRouter\Enum\HttpMethod; #[RouteGroup(pathPrefix: '/users', methods: [HttpMethod::GET])] class ApiUserController { #[Route('/')] public function getUsers() { // Handle getting users } #[Route('/{id}', rules: ['id' => '\d+'])] public function getUserById(int $id) { // Handle getting a user by ID } }
路由扫描器扫描提供的类以查找扩展了RouteInterface
和RouteGroupInterface
的属性。它从这些属性中提取路由配置,并在RouteMap
中注册路由,这是一个包含所有定义的路由的数据结构。
通过调用RouteScanner
类的scanRoutes
方法,并将ApiController
类作为参数传递,可以扫描该类中定义的路由,并在RouteMap
中注册。
use Hotaruma\HttpRouter\RouteScanner\RouteScanner; $routeScanner = new RouteScanner(); $routeMap = $routeScanner->scanRoutes(ApiController::class); $routes = $routeMap->getRoutes(); // ...
路由扫描器可以在RouteMap
及其组中使用。
use Hotaruma\HttpRouter\RouteMap; $routeMap = new RouteMap(); $routeMap->scanRoutes(UserController::class, PostController::class); $routeMap->group( pathPrefix: 'admin', middlewares: [AdminAccessMiddleware::class], group: function (RouteMapInterface $routeMap) { $routeMap->scanRoutes(AdminController::class); } );
当使用$routeMap->scanRoutes()
扫描路由时,遇到RouteGroup
属性,该属性中定义的配置将覆盖由$routeMap->group()
设置的当前组配置。这意味着RouteGroup
中指定的配置将用于特定类中的路由。
routeActionBuilder
方法允许您根据类名和方法名自定义创建的路由的动作外观。如果您想修改路由动作生成的默认行为,这可能很有用。
$routeScanner->routeActionBuilder(function (string $className, string $methodName): array { return "$className@$methodName"; });
scanRoutesFromDirectory
函数允许您扫描指定目录及其子目录中的所有PHP文件,以自动发现带有Route
和RouteGroup
属性的类及其属性。
use Hotaruma\HttpRouter\RouteScanner\RouteScanner; $routeScanner = new RouteScanner(); $directoryPath = __DIR__ . '/Controllers'; $routeMap = $routeScanner->scanRoutesFromDirectory($directoryPath); $routes = $routeMap->getRoutes();
路由调度器
一旦定义了路由,您可以使用RouteDispatcher
类将传入的请求匹配到相应的路由,并提取相关属性。您可以配置RouteDispatcher
并匹配路由。
use Hotaruma\HttpRouter\{RouteMap,RouteDispatcher}; $routeDispatcher = new RouteDispatcher(); $routeMap = new RouteMap(); $routeMap->get('/home', HomeController::class); $routeMap->post('/contacts', ContactsController::class); $routeDispatcher->config( requestHttpMethod: HttpMethod::tryFrom($serverRequest->getMethod()), requestPath: $serverRequest->getUri()->getPath(), routes: $routeMap->getRoutes(), ); try { $route = $routeDispatcher->match(); } catch (RouteDispatcherNotFoundException $exception) { // exception handling } $attributes = $route->getAttributes(); $action = $route->getAction();
您可以通过提供自己的RouteMatcherInterface
接口的实现来自定义路由分发过程。
$routeDispatcher->routeMatcher(new RouteMatcher());
URL生成器
要使用RouteUrlGenerator
,您需要创建一个带有定义的路由的RouteMap
实例。
use Hotaruma\HttpRouter\{RouteMap, RouteUrlGenerator}; $routeUrlGenerator = new RouteUrlGenerator(); $routeMap = new RouteMap(); $routeMap->get('/profile/{category}/', stdClass::class)->config( rules: ['category' => '\w+'], defaults: ['category' => 'projects'], name: 'profile', ); $routeUrlGenerator->config( routes: $routeMap->getRoutes(), ); $route = $routeUrlGenerator->generateByName('profile'); $url = $route->getUrl(); //profile/projects/
您可以通过提供自己的RouteUrlBuilderInterface
接口的实现来自定义路由URL生成。
$routeUrlGenerator->routeUrlBuilder(new RouteUrlBuilder());
贡献
欢迎贡献!如果您发现了一个错误或有一个新功能的想法,请打开一个问题或提交一个拉取请求。