编程式路由

编程式路由指在路由定义文件(默认 config/route/route.php,可经 router.route_config_files 扩展)中通过 Router 门面静态调用注册路由。适合集中管理跨控制器的路由、全局兜底路由与快速原型。文件在服务启动时加载一次,修改后需重启服务。

注册基本路由

通过 Viswoole\Router\Facade\Router 门面,按 HTTP 方法注册路由,第一个参数支持传字符串或多路径数组:

php
<?php
// config/route/route.php
use App\Controller\UserController;
use Viswoole\Router\Facade\Router;

Router::get('/user/info', [UserController::class, 'info']);      // GET  /user/info
Router::post('/user/create', [UserController::class, 'create']); // POST /user/create
Router::put('/user/update', [UserController::class, 'update']);  // PUT  /user/update
Router::delete('/user/delete', [UserController::class, 'delete']);
Router::patch('/user/patch', [UserController::class, 'patch']);
Router::head('/user/head', [UserController::class, 'head']);
Router::options('/user/options', [UserController::class, 'options']);
Router::any('/ping', function () {                               // 任意方法
  return ['pong' => true];
});

快捷方法一览

方法签名说明
get(string|array $paths, $handler): Route注册 GET 路由
post / put / delete / patch / head / options同上注册对应方法的路由
any(string|array $paths, $handler): Route不限制请求方法
addRoute(string|array $paths, $handler, string ...$method): Route按传入方法名注册一条或多条
group(string|array $prefix, Closure $closure, string $id): Group路由分组,$id 必填
miss(Closure $handler, string|array $method = '*'): void兜底路由
server(string $serverName, Closure $closure): void仅在指定服务运行时执行闭包内的路由注册
getRoute(string $idOrCiteLink): Route|Group按 id 或引用链路查询路由实例
getRoutes(): array获取全部顶层路由

INFO

同一路径按不同方法注册多条路由是合法的(RESTful 风格),匹配时按请求方法选择处理器;仅当同一路径且同一方法重复定义时才会抛出路由 id 冲突异常。

处理器的五种形式

$handler 支持以下写法,调用时统一经容器解析并注入依赖:

php
use App\Controller\UserController;
use Viswoole\HttpServer\Contract\RequestInterface;

// 1. 闭包(参数由容器自动注入,如 RequestInterface)
Router::get('/time', function (RequestInterface $request) {
  return ['path' => $request->getPath()];
});

// 2. '类@方法' 字符串
Router::get('/user/info', 'App\Controller\UserController@info');

// 3. [类名, 方法名] 数组(推荐,静态分析友好)
Router::get('/user/list', [UserController::class, 'list']);

// 4. '类::方法' 字符串(适用于静态方法)
Router::get('/config/all', 'App\Controller\ConfigController::all');

// 5. 全局函数名
Router::get('/health', 'app_health_check');

处理器返回值如何转为 HTTP 响应(数组自动 JSON、ResponseInterface 直接发送等)见响应对象

动态路由参数

必选与可选参数

路径中使用 {参数名} 声明必选参数,{参数名?} 声明可选参数(缺失时由处理器方法参数默认值兜底):

php
// GET /user/1024 → $id = '1024';GET /user/ → 404
Router::get('/user/{id}', [UserController::class, 'detail']);

// GET /list → $page = 1;GET /list/3 → $page = '3'
Router::get('/list/{page?}', [UserController::class, 'list']);

动态参数会按名注入处理器的同名方法参数(如 /user/1024 传入 detail(int $id))。

setPatterns 正则约束

setPatterns() 限定变量格式,正则不匹配则该路由不命中:

php
Router::get('/article/{category}/{id}', [ArticleController::class, 'show'])
  ->setPatterns([
    'category' => '[a-z]+',
    'id'       => '\d+',
  ]);

约束规则:

  • 键为变量名,值为非空且合法的正则字符串(注册时校验,非法直接抛异常)
  • 未约束的变量使用全局 router.default_pattern_regex(默认 [\w\.]+
  • 变量名必须以字母或下划线开头、仅含字母数字下划线、不超过 32 字符,同一路径内不可重复

路由分组

Router::group() 把一批路由组织到统一前缀下,第三个参数 $id必填(非注解路由框架无法生成稳定 id,需手动指定且不可重复):

php
Router::group('/api/v1', function () {
  // 相对路径:与分组前缀拼接
  Router::get('user/info', [UserController::class, 'info']);
}, 'api.v1');
// 实际路由: GET /api/v1/user/info

路径合并规则

子路由是否以 / 开头决定与前缀的合并方式:

子路由写法语义合并结果(前缀 /api/v1
user/info相对路径/api/v1/user/info(拼接前缀)
/user/info绝对路径/user/info忽略前缀
/前缀本身/api/v1(分组默认入口)

嵌套分组遵循同样规则,内层分组相对路径(如 'v1')会与外层前缀拼接:

php
Router::group('/api', function () {
  Router::group('v1', function () {
    Router::get('user/info', [UserController::class, 'info']);
  }, 'api.v1');
}, 'api');
// 实际路由: GET /api/v1/user/info

WARNING

分组的链式设置器(如 setMiddlewares())应在 group() 返回值上、闭包定义之后直接链式调用。分组内子路由在路由解析阶段才实例化,此时会继承分组已设置的属性(中间件、域名、后缀等)。

兜底路由(miss)

所有路由均未匹配(404、方法不允许等)时执行 miss 回调,$method 参数可为特定请求方法注册专属兜底;匹配顺序为「精确方法 → '*'」:

php
Router::miss(function () {
  return ['code' => 404, 'message' => 'Not Found'];
});

// 仅为 GET 请求注册兜底
Router::miss(function () {
  return ['code' => 404, 'message' => '资源不存在'];
}, 'GET');

miss 只接受闭包;需要复用控制器逻辑时,在闭包中调用控制器方法即可。

服务级路由

Router::server() 内的路由定义仅在对应名称的服务运行时生效,适合多服务共用一份路由文件(服务名不区分大小写):

php
Router::server('http', function () {
  Router::get('/', [HomeController::class, 'index']);
});

链式设置器

get/post/... 返回 Route 实例、group 返回 Group 实例,两者共享一批链式设置器(均返回 $this):

方法说明
setMethod(string|array ...$method)覆盖允许的请求方法
setMiddlewares(array $middlewares)追加中间件(校验合法性)
setPatterns(array $patterns)追加动态变量正则约束
setMeta(array $meta)追加自定义元数据(关联数组)
setSuffix(string ...$suffix)覆盖伪静态后缀
setDomain(string ...$domain)覆盖生效域名
setTitle(string $title)设置文档标题
setDescription(string $description)设置文档描述
setHidden(bool $flag)是否在 API 文档中隐藏
setSort(int $sort)排序,越大越靠前

Route 额外支持文档维护设置器:setTags(string ...$tag)setStatus(Status $status)setAuthor()setCreatedAt()setUpdatedAt()

php
Router::get('/user/{id}', [UserController::class, 'detail'])
  ->setPatterns(['id' => '\d+'])
  ->setMiddlewares([AuthMiddleware::class])
  ->setTitle('用户详情')
  ->setSort(10);

完整示例

php
<?php
// config/route/route.php
use App\Controller\UserController;
use App\Middleware\AuthMiddleware;
use Viswoole\Router\Facade\Router;

// API 分组
Router::group('/api/v1', function () {
  // 用户模块(需认证)
  Router::group('user', function () {
    Router::get('info', [UserController::class, 'info']);
    Router::get('{id}', [UserController::class, 'detail'])
      ->setPatterns(['id' => '\d+']);
    Router::post('create', [UserController::class, 'create']);
  }, 'api.v1.user')->setMiddlewares([AuthMiddleware::class]);
}, 'api.v1');

// 首页(绝对路径路由)
Router::get('/', [UserController::class, 'home']);

// 404 兜底
Router::miss(function () {
  return ['code' => 404, 'message' => '页面不存在'];
});

下一步

  • 中间件:为路由挂载中间件与理解执行顺序
  • 注解路由:控制器内部路由推荐使用注解声明
  • 路由配置route_config_files 与全局匹配规则