注解路由

注解路由利用 PHP 8 的 Attributes(注解)特性,在控制器类和方法上直接声明路由规则。相比配置文件方式,注解路由使路由定义与业务代码紧密关联,更利于维护和阅读。

三种注解类型

注解作用域说明
#[Controller]类级别声明控制器分组前缀和共享中间件
#[AutoController]类级别自动将该类所有 public 方法注册为路由
#[RouteMapping]方法级别精确控制单个方法的路由规则

Controller 注解

#[Controller] 为控制器设置分组级别的公共属性,类中各方法继承这些配置。

基本用法

php
use Viswoole\Router\Annotation\Controller;
use Viswoole\Router\Annotation\RouteMapping;

#[Controller(prefix: '/api/v1')]
class UserController
{
    #[RouteMapping(method: ['GET'], title: '用户信息')]
    public function info(): array
    {
        // 路由: GET /api/v1/info
        // 未指定 paths 时,方法名直接作为路径后段
        return ['name' => 'Viswoole'];
    }

    #[RouteMapping(paths: ['user-list'], method: ['GET'], title: '用户列表')]
    public function userList(): array
    {
        // 路由: GET /api/v1/user-list
        return [];
    }
}

路径合并规则

方法级 paths 与类级 prefix 的合并规则与配置文件路由分组完全一致(详见路由分组):

paths 写法语义合并结果(类前缀 /api/v1
user-list相对路径/api/v1/user-list(拼接类前缀)
/health绝对路径/health忽略类前缀
/(单独斜杠)类前缀/api/v1(类默认入口)
  • 未指定 paths 时默认为方法名(不带 /),因此始终与类前缀拼接;
  • / 开头的绝对路径会脱离类前缀直接注册为根路由,适合在控制器内声明健康检查等公共入口;
  • 未指定类 prefix 时默认为类短名(不带 /),如 UserController/usercontroller

注意#[Controller] 仅注册带有 #[RouteMapping] 注解的方法,普通 public 方法不会自动成为路由。如需将所有 public 方法自动注册为路由,应使用 #[AutoController]

配置中间件

php
#[Controller(
    prefix: '/api/v1',
    middlewares: [AuthMiddleware::class, RateLimitMiddleware::class],
)]
class ApiController
{
    // 所有方法自动继承上述中间件
}

配置域名

php
#[Controller(prefix: '/api', domain: 'api.example.com')]
class ApiV1Controller
{
    // 仅在 api.example.com 域名下生效
}

AutoController 注解

#[AutoController] 自动扫描控制器的所有 public 方法并生成路由,无需逐个标注。

基本用法

php
use Viswoole\Router\Annotation\AutoController;

#[AutoController(prefix: '/admin')]
class AdminController
{
    public function dashboard(): string
    {
        // 路由: GET /admin/dashboard
        return 'Dashboard';
    }

    public function settings(): array
    {
        // 路由: GET /admin/settings
        return [];
    }
}

注意:AutoController 设置 prefix 后,方法路径为 prefix/methodname,不会重复添加类名段。如需自定义路径,请使用 #[RouteMapping]

RouteMapping 注解

#[RouteMapping] 提供最精细的路由控制,可在方法级别灵活设置单个方法的路由规则(路径、方法、中间件、参数约束等)。

注意#[RouteMapping] 必须配合类级 #[Controller](或 #[AutoController])注解使用——框架只扫描带有类级控制器注解的类,单独的 #[RouteMapping] 不会注册路由。以下示例仅展示 #[RouteMapping] 的写法,实际使用时请结合类级注解。

完整参数说明

php
#[RouteMapping(
    paths: ['user/info', 'user/profile'],  // 支持多路径映射到同一方法
    method: ['GET', 'POST'],                 // 允许的 HTTP 方法
    middlewares: [AuthMiddleware::class],     // 方法级中间件
    patterns: ['id' => '\d+'],                // 动态参数正则约束
    domain: 'api.example.com',                // 域名限制
    suffix: '',                               // 后缀要求(空字符串表示无后缀)
    title: '用户信息',                         // API 文档标题
    description: '获取用户基本信息',            // API 文档描述
)]
public function info(int $id): array {}

参数详解

参数类型默认值说明
pathsstring|array|nullnull路径列表,支持多路径映射同一处理器;未设置时默认为方法名
methodstring|string[]|nullnull允许的 HTTP 方法列表;未设置时继承全局 router.method 配置(默认 '*',即不限制)
middlewaresarray|nullnull中间件类名数组,会追加到类级中间件之后
patternsarray<string, string>|nullnull动态参数名 → 正则表达式
domainstring|array|nullnull限制生效的域名
suffixstring|array|nullnullURL 后缀要求
titlestring''API 文档中的接口标题
descriptionstring''API 文档中的接口描述
sortint0排序,数值越大越靠前
tagsstring[][]API 文档中的接口标签列表
statusStatusStatus::DEVELOPMENT接口状态(已发布/开发中/已废弃等)
hiddenboolfalse是否在 API 文档中隐藏该接口

单一路径

php
#[RouteMapping(paths: ['profile'], method: ['GET'], title: '个人资料')]
public function profile(): array
{
    return ['id' => 1, 'name' => 'Test'];
}

同路径多方法

同一路径允许在不同方法上以不同请求方式定义路由(RESTful 风格常用),分发时按请求方法选择对应处理器:

php
#[RouteMapping(paths: ['profile'], method: ['GET'], title: '获取个人资料')]
public function profile(): array
{
    return ['id' => 1, 'name' => 'Test'];
}

#[RouteMapping(paths: ['profile'], method: ['PUT'], title: '更新个人资料')]
public function updateProfile(array $data): array
{
    return ['updated' => true];
}

注意:仅当同一路径且同一请求方式重复定义时才会触发"重复定义即覆盖"告警。 API 文档会为两个接口生成独立入口,各自拥有独立的参数与返回值声明。

多路径映射

php
#[RouteMapping(
    paths: ['me', 'my/profile', 'user/profile'],
    method: ['GET'],
    title: '当前用户信息',
)]
public function me(): array
{
    return ['user' => '当前用户'];
}

动态参数

php
#[RouteMapping(
    paths: ['article/{category}/{id}'],
    method: ['GET'],
    patterns: [
        'category' => '[a-zA-Z]+',
        'id'       => '\d+',
    ],
    title: '文章详情',
)]
public function article(string $category, int $id): array
{
    return compact('category', 'id');
}

// 请求: GET /article/php/42
// 结果: $category = 'php', $id = 42

可选参数

php
#[RouteMapping(paths: ['search/{keyword?}'], method: ['GET'])]
public function search(string $keyword = ''): array
{
    // /search         → $keyword = ''
    // /search/viswoole → $keyword = 'viswoole'
    return ['keyword' => $keyword];
}

混合使用示例

以下展示 #[Controller]#[RouteMapping] 组合的实际应用场景:

php
<?php
namespace App\Controller\Api\V1;

use App\Middleware\AuthMiddleware;
use Viswoole\Router\Annotation\Controller;
use Viswoole\Router\Annotation\RouteMapping;

#[Controller(
    prefix: '/api/v1',
    middlewares: [AuthMiddleware::class],
)]
class OrderController
{
    #[RouteMapping(
        paths: ['order/list'],
        method: ['GET'],
        title: '订单列表',
        description: '分页获取当前用户的订单记录',
    )]
    public function list(int $page = 1, int $pageSize = 20): array
    {
        return OrderService::paginate($page, $pageSize);
    }

    #[RouteMapping(
        paths: ['order/{id}'],
        method: ['GET'],
        patterns: ['id' => '\d+'],
        title: '订单详情',
    )]
    public function detail(int $id): array
    {
        return OrderService::find($id);
    }

    #[RouteMapping(
        paths: ['order/create'],
        method: ['POST'],
        title: '创建订单',
        middlewares: [ValidateOrderMiddleware::class], // 额外追加中间件
    )]
    public function create(Request $request): array
    {
        return OrderService::create($request->all());
    }
}

注解优先级

当同一控制器同时使用多种注解时,优先级从高到低为:

  1. #[RouteMapping] 方法级配置(最高优先级)
  2. #[AutoController] / #[Controller] 类级配置

方法级 #[RouteMapping] 与类级继承配置的合并规则:

  • paths:按路径合并规则与类级 prefix 合并——不以 / 开头时拼接为完整路径;以 / 开头时忽略类前缀直接作为根路由;
  • middlewares追加合并到类级继承的中间件列表之后;
  • patterns:按参数名合并,方法级同名正则覆盖类级约束;
  • method / domain / suffix 等:仅在方法上显式设置时覆盖类级继承值。