fiberphp / router
FiberPHP router: attribute-based routing with dispatcher, middleware pipeline, controller DI injection and route caching.
Requires
- php: >=8.3
- fiberphp/event: dev-master
- fiberphp/framework: dev-master
- fiberphp/http: dev-master
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2026-08-22 13:29:44 UTC
README
应用导航层,负责路由注册、匹配、调度、中间件管线与控制器参数注入。通过 RequestHandlerInterface 与 fiberphp/http 解耦——HTTP 层只管协议通信,路由层只管请求分发。
环境要求
- PHP >= 8.3
- 依赖
fiberphp/framework、fiberphp/http、fiberphp/event、fiberphp/container
架构
fiberphp/http (物理通信层)
└─ onMessage → container()->get(RequestHandlerInterface)
└─ RouterProvider 绑定 → Router::handleRequest()
├─ Dispatcher (路由匹配:静态路径 O(1) + 变量路径正则)
├─ Pipeline (中间件管线:全局 → 控制器 → 路由)
└─ Controller DI (反射注入:Request/标量/Enum/Model/类)
路由定义策略
| 优先级 | 方式 | 适用场景 |
|---|---|---|
| 一级(主力) | PHP 8 Attribute 注解 | 所有业务 API、Web 页面、Restful 资源 |
| 二级(辅助) | 闭包/数组文件 route/app.php | 系统探针、健康检查、极简重定向 |
路由加载顺序
- 路由缓存存在 → 直接反序列化
runtime/cache/routes.php - 无缓存 →
AttributeScanner扫描控制器注解 → 加载route/*.php闭包文件
配置
路由包无需配置文件,默认行为代码内置:
- 控制器扫描目录固定为
App\Controller\(约定,RouterProvider 内置) - 无注解控制器的默认回退路由默认开启(AttributeScanner 内置,可用
#[Route\NoDefaultRoute]类级注解禁用单个控制器)
应用路由 route/app.php(闭包/数组回调,需安装本包):
use FiberPHP\Http\Response;
use FiberPHP\Router\Router;
Router::get('/ping', fn () => 'pong');
Router::get('/health', fn () => 'ok');
Router::get('/favicon.ico', fn () => new Response(204));
业务路由请使用控制器注解(可缓存);闭包路由无法缓存,生产
route:cache后此文件不加载。
路由定义
1. Attribute 注解(推荐)
namespace App\Controller;
use FiberPHP\Router\Attribute as Route;
use FiberPHP\Http\Request;
use FiberPHP\Http\Response;
class UserController
{
#[Route\Get('/users', name: 'users.index')]
public function index(Request $request): Response
{
return json(['data' => []]);
}
#[Route\Get('/users/{id}', name: 'users.show')]
public function show(int $id): Response
{
return json(['id' => $id]);
}
#[Route\Post('/users', name: 'users.store')]
public function store(Request $request): Response
{
return json(['code' => 0], 201);
}
#[Route\Put('/users/{id}', name: 'users.update')]
public function update(int $id, Request $request): Response
{
return json(['id' => $id]);
}
#[Route\Delete('/users/{id}', name: 'users.destroy')]
public function destroy(int $id): Response
{
return json(['code' => 0]);
}
}
可用的 HTTP 方法注解
| 注解 | HTTP 方法 |
|---|---|
#[Route\Get] | GET |
#[Route\Post] | POST |
#[Route\Put] | PUT |
#[Route\Patch] | PATCH |
#[Route\Delete] | DELETE |
#[Route\Head] | HEAD |
#[Route\Options] | OPTIONS |
#[Route\Any] | 以上全部 |
Route 基类参数
所有 HTTP 方法注解继承 Route 抽象类,构造参数:
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
abstract class Route
{
public function __construct(
public string $path = '', // 路由路径
public array $middleware = [], // 路由中间件
public string $name = '', // 路由名称
public array $where = [], // 参数正则约束
public bool $autoPrefix = true, // 是否自动拼接组前缀
) {}
}
同一个方法可叠加多个注解(IS_REPEATABLE):
#[Route\Get('/users', name: 'users.index')]
#[Route\Get('/users/list', name: 'users.list')]
public function index(): Response { ... }
2. 路由分组 #[RouteGroup]
#[Route\RouteGroup(
prefix: '/api',
middleware: ['auth'],
namePrefix: 'api.',
)]
class UserController
{
// → GET /api/users, 名:api.users.index
#[Route\Get('/users', name: 'users.index')]
public function index() { ... }
}
| 参数 | 说明 |
|---|---|
prefix | 路径前缀,拼接到组内所有路由 |
middleware | 组级中间件,对组内所有路由生效 |
namePrefix | 名称前缀,拼接到组内所有路由名 |
version | 显式版本号覆盖(如 'v2'),替换自动推断的版本段 |
autoPrefix | 是否启用命名空间自动前缀推断(默认 true) |
3. 资源路由 #[Resource]
#[Route\Resource(name: 'posts', only: ['index', 'show', 'store', 'update', 'destroy'])]
class PostController
{
public function index() { ... }
public function show(int $id) { ... }
public function store(Request $request) { ... }
public function update(int $id, Request $request) { ... }
public function destroy(int $id) { ... }
}
自动注册的路由:
| HTTP 方法 | 路径 | 控制器方法 | 路由名称 |
|---|---|---|---|
| GET | /posts | index | posts.index |
| GET | /posts/create | create | posts.create |
| POST | /posts | store | posts.store |
| GET | /posts/{id} | show | posts.show |
| GET | /posts/{id}/edit | edit | posts.edit |
| PUT | /posts/{id} | update | posts.update |
| DELETE | /posts/{id} | destroy | posts.destroy |
| PUT | /posts/{id}/recovery | recovery | posts.recovery |
only 白名单控制只注册部分动作。不传 only 则根据控制器是否存在对应方法自动注册。
4. 闭包路由(应用级)
// route/app.php
use App\Controller\WebhookController;
use FiberPHP\Router\Router;
Router::get('/ping', fn () => 'pong');
Router::post('/webhook', [WebhookController::class, 'handle']);
| 方法 | 说明 |
|---|---|
Router::get($path, $callback) | 注册 GET 路由 |
Router::post($path, $callback) | 注册 POST 路由 |
Router::put($path, $callback) | 注册 PUT 路由 |
Router::patch($path, $callback) | 注册 PATCH 路由 |
Router::delete($path, $callback) | 注册 DELETE 路由 |
Router::any($path, $callback) | 匹配所有方法 |
Router::add($methods, $path, $callback) | 自定义方法(可传数组) |
Router::group($path, $callback) | 路由分组(支持嵌套) |
Router::resource($name, $controller, $options) | 资源路由 |
Router::getByName($name) | 按名称获取路由对象 |
Router::getRoutes() | 获取所有路由 |
API 版本管理
采用"模块化 + 版本子目录"架构,从控制器命名空间自动推断版本前缀。
自动推断规则
App\Controller\V1\UserController → /v1/users 名称前缀: v1.
App\Controller\V2\UserController → /v2/users 名称前缀: v2.
App\Controller\V2\Admin\RoleController → /v2/admin/roles 名称前缀: v2.admin.
V\d+命名空间段识别为版本号,转为小写vN作为路径和名称前缀- 其余段转为 snake_case 拼入路径
- 类名转为 snake_case 作为资源路径
示例
namespace App\Controller\V1;
use FiberPHP\Router\Attribute as Route;
// 自动推断:前缀 /v1, 名称前缀 v1.
class UserController
{
#[Route\Get('/users', name: 'users.index')] // → GET /v1/users, 名:v1.users.index
public function index() { ... }
}
namespace App\Controller\V2;
use FiberPHP\Router\Attribute as Route;
// 自动推断:前缀 /v2, 名称前缀 v2.
class UserController
{
#[Route\Get('/users', name: 'users.index')] // → GET /v2/users, 名:v2.users.index
public function index() { ... }
}
显式版本覆盖
#[Route\RouteGroup(version: 'v3')]
class UserController
{
// 即使类在 V1 命名空间下,路径也会是 /v3/users
#[Route\Get('/users')]
public function index() { ... }
}
默认回退路由
当控制器没有任何方法注解时,默认开启回退路由,自动将所有 public 方法注册为 ANY 路由:
namespace App\Controller;
class IndexController
{
public function index() // → ANY /index
{
return 'hello';
}
public function healthCheck() // → ANY /health_check
{
return 'ok';
}
}
方法名自动转为 snake_case 作为路径。使用 #[Route\NoDefaultRoute] 可禁止此行为:
#[Route\NoDefaultRoute]
class ApiController
{
// 不会自动注册任何路由
public function internalMethod() { ... }
}
路由参数
动态参数
#[Route\Get('/users/{id}')]
public function show(int $id) { ... }
可选参数
#[Route\Get('/posts/{id?}')]
public function show(int $id = 1) { ... }
正则约束
通过 where 参数约束参数格式:
#[Route\Get('/users/{id}', where: ['id' => '\d+'])]
public function show(int $id) { ... }
// 路径编译为 /users/{id:\d+}
路由命名与 URL 生成
#[Route\Get('/users/{id}', name: 'users.show')]
public function show(int $id) { ... }
// 通过名称获取路由
$route = Router::getByName('users.show');
// 生成 URL
$url = $route->url(['id' => 42]);
// /users/42
// 多余参数自动拼为查询字符串
$url = $route->url(['id' => 42, 'tab' => 'profile']);
// /users/42?tab=profile
中间件
执行顺序:全局中间件 → 控制器中间件 → 路由中间件 → 控制器方法。
全局中间件
在 config/http.php 的 middleware.global 键注册:
'middleware' => [
'global' => [
\App\Middleware\CorsMiddleware::class,
\App\Middleware\TraceMiddleware::class,
],
'aliases' => [
'auth' => \App\Middleware\AuthMiddleware::class,
],
],
中间件别名
middleware.aliases 将短名映射到中间件类,路由/控制器中间件可用别名替代完整类名:
// config/http.php
'middleware' => [
'aliases' => [
'auth' => \App\Middleware\AuthMiddleware::class,
'admin' => \App\Middleware\AdminMiddleware::class,
],
],
// 路由注解用别名
#[Route\Get('/admin', middleware: ['auth', 'admin'])]
控制器中间件
class AdminController
{
protected static array $middleware = [
\App\Middleware\AuthMiddleware::class,
];
}
路由中间件
通过注解的 middleware 参数指定:
#[Route\Get('/admin/dashboard', middleware: ['auth', 'admin'])]
public function dashboard() { ... }
通过 #[RouteGroup] 批量指定:
#[Route\RouteGroup(prefix: '/api', middleware: ['auth'])]
class UserController { ... }
中间件实现
中间件类需实现 FiberPHP\Http\Contract\MiddlewareInterface:
use FiberPHP\Http\Contract\MiddlewareInterface;
use FiberPHP\Http\Request;
use FiberPHP\Http\Response;
class AuthMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
if (!$request->header('authorization')) {
return new Response(401, [], 'Unauthorized');
}
return $handler($request);
}
}
控制器参数注入
基于反射的自动参数注入,首次请求分析方法签名并缓存元数据。
| 参数类型 | 注入方式 |
|---|---|
Request(或其父类) | 注入当前请求对象 |
| 标量(int/float/bool/string/array) | 从请求参数获取并自动类型转换 |
mixed/resource | 直接透传请求参数值 |
| 枚举(Enum) | 按名称或 backed 值匹配 |
FiberPHP\Model 子类 | 以请求数据为 attributes 构造 |
| 其他类(有构造函数) | 递归解析构造参数,容器构造 |
| 其他类(无构造参数) | 容器实例化 |
use FiberPHP\Http\Request;
use App\Service\UserService;
use App\Enum\UserStatus;
class UserController
{
// Request + 路由参数
public function show(Request $request, int $id) { ... }
// 标量 + 默认值
public function search(string $keyword, int $page = 1) { ... }
// 依赖注入
public function create(UserService $service) { ... }
// 枚举注入
public function status(UserStatus $status) { ... }
}
缺少必填参数抛 InputException(HTTP 400),类型不匹配同样抛 InputException。
路由缓存
# 编译路由缓存(仅支持控制器动作,闭包不可缓存)
php fiber route:cache
# 清理路由缓存
php fiber route:clear
缓存文件生成在 runtime/cache/routes.php,存在时直接反序列化加载,跳过注解扫描。
route/app.php闭包路由无法缓存,route:cache会抛RuntimeException。生产环境建议使用控制器动作。
命令行工具
# 查看所有已注册路由
php fiber route:list
输出表格包含 URI、HTTP 方法、回调、中间件、路由名称。
与 fiberphp/http 的关系
fiberphp/http fiberphp/router
┌─────────────────────┐ ┌─────────────────────────┐
│ TCP 连接管理 │ │ 控制器注解扫描 │
│ HTTP 协议解析 │ │ 路由匹配 (Dispatcher) │
│ 全局中间件管线 │ │ 控制器/路由中间件 │
│ 异常渲染 │ Request │ 控制器 DI 注入 │
│ 连接保活 │ ────────→ │ 版本前缀推断 │
│ │ Response │ 路由缓存 │
│ RequestHandlerIntf │←──────────│ Router::handleRequest() │
│ (Contract 层) │ 绑定 │ RouterProvider │
└─────────────────────┘ └─────────────────────────┘
解耦点:FiberPHP\Http\Contract\RequestHandlerInterface
fiberphp/http的Http::onMessage()通过容器获取RequestHandlerInterface实现fiberphp/router的RouterProvider::register()绑定该接口到匿名类,委托Router::handleRequest()- 若未安装
fiberphp/router,容器返回 null,回退到defaultHandler()直接抛NotFoundException(404)
目录结构
src/
├── Attribute/ # 路由注解
│ ├── Route.php # 抽象基类(path/middleware/name/where/autoPrefix)
│ ├── Get.php # GET 注解
│ ├── Post.php # POST 注解
│ ├── Put.php # PUT 注解
│ ├── Patch.php # PATCH 注解
│ ├── Delete.php # DELETE 注解
│ ├── Head.php # HEAD 注解
│ ├── Options.php # OPTIONS 注解
│ ├── Any.php # 全方法注解
│ ├── RouteGroup.php # 类级分组(prefix/middleware/namePrefix/version)
│ ├── Resource.php # 类级 RESTful 资源
│ └── NoDefaultRoute.php # 禁止默认回退路由
├── Command/ # 控制台命令
│ ├── RouteList.php # route:list
│ ├── RouteCache.php # route:cache
│ └── RouteClear.php # route:clear
├── AttributeScanner.php # 控制器注解扫描 + 版本前缀推断 + 回退路由
├── Dispatcher.php # 路由编译与匹配(静态 O(1) + 变量正则)
├── DispatchStatus.php # 调度状态枚举(Found/NotFound/MethodNotAllowed)
├── Route.php # 路由值对象(name/middleware/params/url)
├── Router.php # 路由注册入口 + 请求调度 + 中间件管线 + DI 注入
├── RouterProvider.php # 服务提供者(绑定接口 + 加载路由)
└── Install.php # 安装器(#[Package] 声明)
License
MIT