创建控制器
控制器是处理 HTTP 请求的入口点,Viswoole 框架通过 PHP 8 注解(Attribute)机制实现控制器的声明式注册。本节将详细介绍如何创建和配置控制器。
基本结构
控制器文件位置
所有控制器必须位于 app/Controller 目录下,命名空间为 App\Controller:
text
app/
└── Controller/
├── UserController.php
├── OrderController.php
└── Example.php最小化控制器示例
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class DemoController
{
/**
* 首页
*/
public static function index(): string
{
return 'Hello Viswoole!';
}
}访问路径:GET /democontroller/index
说明:
#[AutoController]未设置prefix时,路径使用完整类名小写化(如DemoController→democontroller),不去除Controller后缀。
AutoController 注解
#[AutoController] 是最常用的控制器注解,它会自动将类中所有 public 方法注册为路由。
基础用法
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class ProductController
{
/**
* 产品列表
*/
public static function list(): array
{
return ['products' => []];
}
/**
* 产品详情
*
* @param int $id 产品ID
*/
public static function detail(#[InjectGet] int $id): array
{
return ['id' => $id, 'name' => 'Product'];
}
}生成的路由:
GET /productcontroller/list→ProductController::list()GET /productcontroller/detail?id=1→ProductController::detail()
配置选项
php
#[AutoController(
prefix: 'api/v1', // 路由前缀:/api/v1/product/*
sort: 100, // 排序权重(数值越大越靠前)
middlewares: [ // 中间件列表
AuthMiddleware::class,
RateLimitMiddleware::class
]
)]
class ApiController
{
// ...
}参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
prefix | string | '' | 路由前缀,会添加到所有方法路由前 |
sort | int | 0 | 路由排序权重,数值越大越靠前 |
middlewares | array | [] | 应用于该控制器所有方法的中间件 |
Controller 注解(手动模式)
当需要精确控制哪些方法暴露为路由时,使用 #[Controller] 注解配合 #[RouteMapping]。
基础用法
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\Router\Annotation\Controller;
use Viswoole\Router\Annotation\RouteMapping;
#[Controller(prefix: 'admin')]
class AdminController
{
/**
* 登录页面 - 公开访问
*/
#[RouteMapping('login', method: ['GET', 'POST'])]
public static function login(): string
{
return '<h1>Login</h1>';
}
/**
* 仪表盘 - 需要认证
*/
#[RouteMapping('dashboard', method: 'GET')]
#[Auth] // 自定义认证中间件
public static function dashboard(#[InjectGet] string $tab = 'overview'): array
{
return ['tab' => $tab, 'data' => []];
}
/**
* 内部方法 - 不对外暴露
*/
private static function processData(array $data): array
{
// 仅内部调用,不会生成路由
return $data;
}
}生成的路由:
GET|POST /admin/login→AdminController::login()GET /admin/dashboard?tab=overview→AdminController::dashboard()processData()不会注册为路由(private 方法)
RouteMapping 配置
php
#[RouteMapping(
paths: 'user/{id}', // 路由路径,支持动态参数
method: 'GET', // HTTP 方法:GET|POST|PUT|DELETE|*
middlewares: [], // 方法级中间件
patterns: [] // 路由参数约束
)]
public static function show(#[InjectGet] int $id): array
{
return ['id' => $id];
}支持的 HTTP 方法
- 单个方法:
'GET'、'POST'、'PUT'、'DELETE'、'PATCH' - 多个方法:
['GET', 'POST'] - 所有方法:
'*'
动态路由参数
支持在路由路径中定义动态参数,使用 {参数名} 语法:
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class ArticleController
{
/**
* 必选参数
* 路由:/article/show/123
*/
#[RouteMapping('show/{id}')]
public static function show(#[InjectGet] int $id): array
{
return ['article_id' => $id];
}
/**
* 可选参数(带 ?)
* 路由:/article/category/php 或 /article/category
*/
#[RouteMapping('category/{slug?}')]
public static function category(#[InjectGet] ?string $slug = null): array
{
return ['category' => $slug ?? 'all'];
}
}完整示例:RESTful API 控制器
php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Interface\UserInfo;
use App\Service\UserService;
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\HttpServer\AutoInject\InjectPost;
use Viswoole\HttpServer\Contract\ResponseInterface;
use Viswoole\Router\Annotation\AutoController;
use Viswoole\Router\Annotation\RouteMapping;
use App\Response;
/**
* 用户资源控制器
* 实现 RESTful 风格的 CRUD 接口
*/
#[AutoController(
prefix: 'api/v1',
middlewares: [\App\Middleware\ApiLogMiddleware::class]
)]
class UserController
{
/**
* 获取用户列表
*
* GET /api/v1/user/list?page=1&size=10&keyword=test
*
* @param int $page 页码
* @param int $size 每页数量
* @param string|null $keyword 搜索关键词
* @return array 分页数据
*/
#[RouteMapping(method: 'GET')]
public static function list(
#[InjectGet] int $page = 1,
#[InjectGet] int $size = 10,
#[InjectGet] ?string $keyword = null
): array {
return UserService::getList($page, $size, $keyword);
}
/**
* 获取用户详情
*
* GET /api/v1/user/detail?id=100
*
* @param int $id 用户ID
* @return array 用户信息
*/
#[RouteMapping(method: 'GET')]
public static function detail(#[InjectGet] int $id): array
{
return UserService::getDetail($id);
}
/**
* 创建用户
*
* POST /api/v1/user/create
* Body: { "name": "张三", "gender": "male" }
*
* @param UserInfo $userInfo 用户信息 DTO
* @return array 创建结果
*/
#[RouteMapping(method: 'POST')]
public static function create(#[InjectPost] UserInfo $userInfo): array
{
$userId = UserService::create($userInfo);
return ['id' => $userId, 'message' => '创建成功'];
}
/**
* 更新用户信息
*
* PUT /api/v1/user/update
* Body: { "id": 100, "name": "新名称" }
*
* @param UserInfo $userInfo 用户信息 DTO
* @return array 更新结果
*/
#[RouteMapping(method: 'PUT')]
public static function update(#[InjectPost] UserInfo $userInfo): array
{
$result = UserService::update($userInfo);
return ['success' => $result, 'message' => $result ? '更新成功' : '更新失败'];
}
/**
* 删除用户
*
* DELETE /api/v1/user/delete?id=100
*
* @param int $id 用户ID
* @return array 删除结果
*/
#[RouteMapping(method: 'DELETE')]
public static function delete(#[InjectPost] int $id): array
{
$result = UserService::delete($id);
return ['success' => $result, 'message' => $result ? '删除成功' : '删除失败'];
}
/**
* 导出用户数据(返回文件下载响应)
*
* GET /api/v1/user/export?format=csv
*
* @param string $format 导出格式
* @param Response $response 响应对象
* @return ResponseInterface 文件下载响应
*/
#[RouteMapping(method: 'GET')]
public static function export(
#[InjectGet] string $format = 'csv',
Response $response
): ResponseInterface {
$filePath = UserService::export($format);
return $response->sendfile($filePath);
}
}最佳实践
1. 命名规范
php
// ✅ 推荐:使用有意义的名称,复数形式表示集合
class UserController {} // 用户相关
class OrderController {} // 订单相关
class ProductCategoryController {} // 产品分类
// ❌ 避免:模糊或不规范的命名
class Ctrl1 {}
class Uc {}
class user_controller {}2. 方法命名建议
| 操作类型 | 推荐命名 | HTTP 方法 |
|---|---|---|
| 列表 | list, index | GET |
| 详情 | detail, show, read | GET |
| 创建 | create, store, add | POST |
| 更新 | update, edit, modify | PUT/PATCH |
| 删除 | delete, remove, destroy | DELETE |
| 导出 | export, download | GET |
| 其他操作 | 使用动词开头 | 根据语义选择 |
3. 组织大型控制器
当控制器方法过多时(超过 10 个方法),考虑拆分:
text
Controller/
├── User/
│ ├── BasicController.php # 基础信息
│ ├── ProfileController.php # 个人资料
│ └── SecurityController.php # 安全设置
├── Order/
│ ├── ListController.php # 订单列表
│ └── DetailController.php # 订单详情4. 使用依赖注入
对于需要的服务类,通过构造函数或方法参数注入:
php
#[AutoController]
class ReportController
{
/**
* 通过方法参数注入服务
*/
public static function generate(
#[InjectPost] ReportDto $dto,
ReportService $service // 自动从容器注入
): ResponseInterface {
return $service->generate($dto);
}
}常见陷阱
1. 忘记添加 public 修饰符
php
// ❌ 错误:默认方法不是 public,不会被注册
function index(): array
{
return [];
}
// ✅ 正确:显式声明 public
public static function index(): array
{
return [];
}2. 路径大小写问题
URL 路径通常不区分大小写,但为了保持一致性,建议统一使用小写:
php
// ✅ 推荐
#[AutoController(prefix: 'api/v1')]
// ⚠️ 可能导致混淆
#[AutoController(prefix: 'API/V1')]3. 循环依赖问题
避免在控制器中直接实例化其他控制器,应通过 Service 层协调:
php
// ❌ 错误:控制器之间耦合
public static function orderList(): array
{
$userCtrl = new UserController();
return $userCtrl->detail($this->userId);
}
// ✅ 正确:通过 Service 层
public static function orderList(): array
{
return OrderService::getOrdersByUser($this->userId);
}