fiberphp / router
🛣️ FiberPHP 路由器 —— 属性路由声明,支持中间件管道、控制器依赖注入、路由缓存、RESTful 约定。
Requires
- php: >=8.3
- ext-json: *
- fiberphp/framework: dev-master
- fiberphp/helper: dev-master
- fiberphp/http: dev-master
- psr/log: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 02:46:34 UTC
README
注解路由层:扫描控制器注解(#[Get] 等)、加载 route/*.php 路由文件,统一注册到 fiberphp/http 的路由核心;
提供 route:list / route:cache / route:clear 命令。路由注册表、分发、中间件管线与控制器参数注入由 http 包承载。
环境要求
- PHP >= 8.3
- 必需依赖:
fiberphp/framework(含包发现机制、事件总线与#[Package]特性)、fiberphp/http、psr/log - 控制台命令(
route:list/route:cache/route:clear)已随 framework 内置
安装
composer require fiberphp/router
包源码托管在 Gitee(gitee.com/fiberphp/router)。若尚未发布到 Packagist,在应用 composer.json 中添加 VCS 仓库源:
{
"repositories": [
{ "type": "vcs", "url": "https://gitee.com/fiberphp/router.git" }
]
}
当前各包以
dev-master互依赖,应用需设置"minimum-stability": "dev"(骨架包fiberphp/skeleton已内置);发布稳定版后将切换为语义化版本约束。
配置
路由包无需配置文件,默认行为代码内置:
- 控制器扫描目录固定为
App\Controller\ - 无注解控制器由 http 包约定式扫描兜底(类级
#[\FiberPHP\Http\Attribute\NoDefaultRoute]可关闭该类的约定路由)
应用路由 route/app.php(闭包/数组回调,需安装本包):
use FiberPHP\Http\Response;
use FiberPHP\Http\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 User
{
#[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 User
{
// → 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 Post
{
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\Webhook;
use FiberPHP\Http\Router;
Router::get('/ping', fn () => 'pong');
Router::post('/webhook', [Webhook::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\User → /v1/users 名称前缀: v1.
App\Controller\V2\User → /v2/users 名称前缀: v2.
App\Controller\V2\Admin\Role → /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 User
{
#[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 User
{
#[Route\Get('/users', name: 'users.index')] // → GET /v2/users, 名:v2.users.index
public function index() { ... }
}
显式版本覆盖
#[Route\RouteGroup(version: 'v3')]
class User
{
// 即使类在 V1 命名空间下,路径也会是 /v3/users
#[Route\Get('/users')]
public function index() { ... }
}
约定式兜底路由
没有任何方法注解的控制器,由 http 包约定式扫描器按文件路径与方法名自动注册路由(如 Admin/Role.php 的 getList() → GET /admin/role/list)。类级标注 #[\FiberPHP\Http\Attribute\NoDefaultRoute] 可关闭该类的约定路由:
use FiberPHP\Http\Attribute\NoDefaultRoute;
#[NoDefaultRoute]
class Api
{
// 不会自动注册任何约定路由
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) { ... }
// 通过辅助函数生成 URL
$url = route('users.show', ['id' => 42]);
// /users/42
// 通过名称获取路由对象
$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 Admin
{
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 User { ... }
中间件实现
中间件类需实现 FiberPHP\Http\Contract\MiddlewareInterface:
use Closure;
use FiberPHP\Http\Contract\MiddlewareInterface;
use FiberPHP\Http\Request;
use FiberPHP\Http\Response;
class AuthMiddleware implements MiddlewareInterface
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->header('authorization')) {
return new Response(401, [], 'Unauthorized');
}
return $next($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 User
{
// 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) { ... }
}
缺少必填参数或类型不匹配时抛 FiberPHP\Http\Exception\HttpException(HTTP 400,消息透传,出错参数名等细节随 context 仅在 debug 模式输出)。
路由缓存
# 编译路由缓存(仅支持控制器动作,闭包不可缓存)
php fiberphp route:cache
# 清理路由缓存
php fiberphp route:clear
缓存文件生成在 runtime/cache/routes.php,存在时优先于注解扫描加载。
route/app.php闭包路由无法缓存,route:cache会抛RuntimeException。生产环境建议使用控制器动作。
命令行工具
# 查看所有已注册路由
php fiberphp route:list
输出表格包含 URI、HTTP 方法、回调、中间件、路由名称。