hotaruma/http-router

简单的HTTP路由器

v0.7.0 2023-07-09 23:02 UTC

This package is auto-updated.

Last update: 2024-09-12 18:23:07 UTC


README

Build and Test Latest Version License PHP from Packagist Packagist Downloads codecov

简单的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
    }
}

路由扫描器扫描提供的类以查找扩展了RouteInterfaceRouteGroupInterface的属性。它从这些属性中提取路由配置,并在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文件,以自动发现带有RouteRouteGroup属性的类及其属性。

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());

贡献

欢迎贡献!如果您发现了一个错误或有一个新功能的想法,请打开一个问题或提交一个拉取请求。