在 Laravel 中封装路由注解

在 Laravel 中,你可以在 routes 目录下创建路由的声明文件,例如 web.php 或者 api.php。这篇文章描述了另一种方式:通过扫描注解来注册路由。

示例

为了简化路由的编写,可以通过注解的方式来显示路由,也避免了编写路由需要跳转文件。先来看示例:

#[GetMapping(path: '/user/register')]
public function register() {}

看上去,使用注解会比在文件中单独声明要来的方便。

实现

首先你可能要创建一个注解类:

#[Attribute(Attribute::TARGET_METHOD)]
class GetMapping
{
    public function __construct(
        public string $path,
        public string $prefix = 'api'
    ) {}
}

接着,注解注解就是注释加上解释。肯定要在某一个时间点,去解析注释,你可以在 web.php 或者 api.php 中去解析注解。核心逻辑如下:

  1. 扫描控制器目录下 app\Http\Controllers 中的.php 结尾的代码文件
  2. 遍历每一个控制器中的每一个方法,利用反射读取是否包含 #[GetMapping] 之类的注解
  3. 如果包含路由注解,则获取注解中的参数,调用 Route 门面类自动注册路由

扫描控制器目录下的文件

我们先来看第 1 步,扫描控制器目录下的代码文件, 示例代码如下:

public static function scanForFiles(string $basePath): array
{
    $directoryIterator = new RecursiveDirectoryIterator($basePath);
    $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);

    $controllerFiles = [];
    foreach ($recursiveIterator as $file) {
        if ($file->isDir() || in_array($file->getFilename(), '.php')) {
            continue;
        }
        if (str_contains($file->getFilename(), 'Controller.php')) {
            $controllerFiles[] = $file->getPathname();
        }
    }
    return $controllerFiles;
}

遍历控制器文件

接着,就是遍历控制器下的文件, 转换文件名到命名空间

foreach ($controllerFiles as $file) {
    // 只是说明一个思路,命名空间转换的代码就不贴出来了
    $controller = ControllerHelper::convertPathToNamespace($file);
    $reflectionClass = new ReflectionClass($controller);

    $methods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
    foreach ($methods as $method) {
        // todo: 扫描注解,自动注册路由
    }
}

扫描注解注册路由

最后一步,就是扫描注解注册路由了:

$attribute = collect($method->getAttributes())
    ->filter(fn (ReflectionAttribute $attribute) => $attribute->getName() === GetMapping::class)->first();
$uri = $attribute->newInstance()->path;
Route::{$routesMapping[$attribute->getName()]}($uri, [$controller, $method->getName()]);

到这里就完成了,还是那句话,封装是允许复杂的,但是使用要简单

总结

这篇文档介绍了如何自己封装路由注解,给出一种实现的思路。

当然你可能觉得不够灵活,如果要我加上一个路由中间件呢?有两种做法:

  1. 给上面这个 #[GetMapping] 增加一个类,支持传入一个中间件的类的数组
  2. 单独实现一个路由中间件的注解

此外,你还可以实现其他的注解 #[PostMapping]#[PutMapping]#[DeleteMapping]#[RequestMapping]#[RestController] 等等,只要你有需要。

这些注解只是用来作为路由的注解吗?其实不是,还可以作为接口文档生成的注解。你可以利用 VitePress 等静态网页的生成方案,解析这些注解,生成 Markdown 文件,然后生成静态的 API 接口的站点,接入 CI/CD…… 只要你敢想敢为,就可以任意发挥。

对了,你还可以用这类注解来自动生成 SDK,如果你的服务是对外的话,比如实现准备好 PHP、Java、Go、Python 的 SDK 项目基础代码,然后通过扫描这些注解,然后生成对应的请求类的代码,就实现了。

在微服务中,你可以利用这些注解,生成每一个服务的 SDK,然后通过 Composer 提供给其他的服务依赖。连版本化也一并解决了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 4
陈先生

使用 \Illuminate\Support\Facades\Route::addRoute('GET', '/test', function () { return 'Hello World'; });

而不是

Route::{$routesMapping[$attribute->getName()]}($uri, [$controller, $method->getName()]);

1周前 评论
苏近之 (楼主) 1周前

路由缓存怎么办?

1周前 评论
苏近之 (楼主) 1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!