devsrealm / tonics-router-system

为 Tonics 项目设计的虚构 PHP 路由系统

1.0.0 2023-06-30 08:47 UTC

This package is auto-updated.

Last update: 2024-10-01 00:09:15 UTC


README

一个基于 Trie 的 PHP 路由系统,用于 Tonics 项目。

这将成为 tonics 网络应用的基础路由器,与大多数 PHP 路由器不同之处在于,它不使用正则表达式来匹配 URL,而是使用一种树形数据结构,其中每个路径都是按层次组织的,这使得查找静态或动态 URL 都更快。

此外,我想出了一个名为节点传送的概念,可以进一步增强和加速动态路由的搜索,在最佳情况下,动态路由可以直接匹配,就像静态路由一样,在最坏的情况下,它会传送几次,这比单纯遍历要快。

你可以在路由器工作原理的第二个部分中了解更多关于传送的信息。

要求

  • PHP 8.0 及以上版本
  • PHP mbstring 扩展已启用。

安装

composer require devsrealm/tonics-router-system

如果你不想使用 composer,请转到发布部分,下载后缀为 composer-no-required 的 zip 文件,例如 tonics-router-system-v1.0.0-composer-no-required.zip

解压它,并按如下方式要求:

require 'path/to/tonics-router-system/vendor/autoload.php';

路由器如何工作

  1. PHP 中更快的路由系统(第 1 部分)
  2. PHP 中更快的路由系统(第 2 部分)(改进与基准测试)

文档

在开始之前,请连接路由器依赖项

$onRequestProcess = new OnRequestProcess(
                        new RouteResolver(
                            new Container()
                        ),
                        new Route(
                            new RouteTreeGenerator(
                                new RouteTreeGeneratorState(), new RouteNode()
                                )
                        )
                );

$router = new Router(
    $onRequestProcess,
    $onRequestProcess->getRouteObject(),
    new Response(
        $onRequestProcess, new RequestInput()
        )
    );

基本路由

第一个参数是要匹配的路由 URL 路径,第二个参数可以是闭包或回调函数,当路由匹配时将调用此路由。

$route = $router->getRoute();

$route->get('/', function() {
    return 'Welcome To My Home Page';
});

如果你想保持整洁,你还可以通过类方法解决,如下所示

$route->get('/', [HomePage::class, 'methodName']);

请求拦截器

有些人称之为中间件,对我来说“requestInterceptor”听起来简单明了。请求拦截器可用于在请求移动到下一个生命周期或转到其他请求拦截器之前拦截请求。

例如,如果你有一个管理员 URL 路径:/admin,并且你想在处理请求之前检查用户是否已登录,你将使用请求拦截器。让我们看看一个例子

$route->get('admin', [AdminController::class, 'adminDashboard'], [IsAuthenticated::class]);

isAuthenticated() 类中,你可以有如下内容

class Authenticated implements TonicsRouterRequestInterceptorInterface
{
    /**
     * @inheritDoc
     */
    public function handle(OnRequestProcess $request): void
    {
       if (UserData::isAuthenticated() === false){
           # If this is for admin, then redirect to admin login
           if (str_starts_with($request->getRequestURL(), '/admin')){
               redirect(route('admin.login'));
           }

           # If this is for customer, then redirect to customer login
           if (str_starts_with($request->getRequestURL(), '/customer')){
               redirect(route('customer.login'));
           }

           # Else...
           SimpleState::displayUnauthorizedErrorMessage();
       }
    }
}

我们实现了 TonicsRouterRequestInterceptorInterface(必须实现该接口才能使用请求拦截器),它提供了一个带有 $request 对象的 handle 方法。在 handle 方法内部,我检查用户是否未认证,并因此将他们重定向到他们的适当目的地。

然而,如果用户已认证,拦截器将移动到路由状态中的下一个生命周期,下一个生命周期可以是新的请求拦截器、类方法或回调委托。

要添加更多请求拦截器,只需这样做

$route->get('admin', 
    [AdminController::class, 'adminDashboard'], 
    [IsAuthenticated::class, MoreInterceptor::class, EvenMoreInterceptor::class]
    );

路由所需参数

要匹配动态 URL 参数,请这样做

$route->get('posts/:slug', function($slug) {
    return "Post with slug: $slug";
});

其中你捕获了 URL 中的 slug,例如,如果用户访问 /posts/blog-post-title,你将访问到 blog-post-title

或者你可以这样做

$route->get('/posts/:slug', [PostsController::class, 'viewPost']);

其中 `PostsController 可能如下所示

class PostsController
{
    viewPost($slug)
    {
        return "Post with slug: $slug";
    }
}

路由分组

使用路由分组,你可以按树形方式组织路由,这种方法的好处是
你可以跨大量路由共享路由属性,例如路由拦截器、父 URL 路径等,而不需要在每个单独的路由上定义这些属性。

而不是这样做

$route->get('admin/login', [LoginController::class, 'showLoginForm'], [SpecialInterceptor::class, RedirectAuthenticated::class]);
$route->post('admin/login', [LoginController::class, 'login'], [SpecialInterceptor::class]);
$route->post('admin/logout', [LoginController::class, 'logout'], [SpecialInterceptor::class]);

这样做

$route->group('admin', function (Route $route){
    $route->get('login', [LoginController::class, 'showLoginForm'], [RedirectAuthenticated::class]);
    $route->post('login', [LoginController::class, 'login']);
    $route->post('logout', [LoginController::class, 'logout']);
}, [SpecialInterceptor::class]);

最终目标与上面相同,但组织得更好。

您还可以嵌套一个组

$route->group('/admin/posts', function (Route $route){
            #---------------------------------
        # POST CATEGORIES...
    #---------------------------------
    $route->group('/category', function (Route $route){
        $route->get('', [PostCategoryController::class, 'index']);
        $route->get(':category/edit', [PostCategoryController::class, 'edit']);
        $route->get('create', [PostCategoryController::class, 'create']);
        $route->post('store', [PostCategoryController::class, 'store']);
        $route->post(':category/trash', [PostCategoryController::class, 'trash']);
        $route->post( '/trash/multiple', [PostCategoryController::class, 'trashMultiple']);
        $route->match(['post', 'put', 'patch'], ':category/update', [PostCategoryController::class, 'update']);
        $route->match(['post', 'delete'], ':category/delete', [PostCategoryController::class, 'delete']);
    }, alias: 'category');

}, [StartSession::class, CSRFGuard::class, Authenticated::class, PostAccess::class]);

路由HTTP动词

  • $route->get(string $url, array|Closure $callback, array $requestInterceptor = [])
  • $route->post(string $url, array|Closure $callback, array $requestInterceptor = [])
  • $route->put(string $url, array|Closure $callback, array $requestInterceptor = [])
  • $route->patch(string $url, array|Closure $callback, array $requestInterceptor = [])
  • $route->delete(string $url, array|Closure $callback, array $requestInterceptor = [])
  • $route->match(array $method, string $url, \Closure|array $callback, array $requestInterceptor = [])

使用match,您可以在一次操作中匹配多个HTTP动词。

更多文档...