本文目录导读:

在 PHP 项目中,控制器的规范编写是保证代码可维护性、可测试性和团队协作效率的关键,以下是基于主流 MVC 框架(如 Laravel、ThinkPHP、Symfony、CodeIgniter 等)的通用规范化建议。
核心设计原则
- 单一职责:一个控制器方法只处理一类请求(如
create、store、show、update、destroy)。 - 瘦控制器(Skinny Controller):控制器不写业务逻辑,只负责调度(接收输入 -> 调用服务 -> 返回输出)。
- 避免重复代码:通过中间件、表单请求验证、资源控制器等机制减少模板代码。
方法命名与结构规范
RESTful 资源控制器(推荐)
使用 RESTful 风格的资源路由,标准方法:
| 动词 | URI | 方法名 | 用途 |
|---|---|---|---|
| GET | /users | index |
显示列表 |
| GET | /users/create | create |
显示创建表单 |
| POST | /users | store |
处理创建请求 |
| GET | /users/{id} | show |
显示单个资源 |
| GET | /users/{id}/edit | edit |
显示编辑表单 |
| PUT/PATCH | /users/{id} | update |
处理更新请求 |
| DELETE | /users/{id} | destroy |
删除资源 |
示例(Laravel):
class UserController extends Controller
{
public function index(Request $request): View
{
$users = UserService::list($request->validated());
return view('users.index', compact('users'));
}
public function store(UserStoreRequest $request): RedirectResponse
{
$user = UserService::create($request->validated());
return redirect()->route('users.show', $user->id)
->with('success', '用户创建成功');
}
}
自定义方法命名规范
- 动词 + 资源:
exportUsers,batchDelete,toggleStatus - 避免使用
doSomething等模糊命名
输入处理规范
使用表单请求验证
将验证逻辑从控制器分离到专门的验证类中。
// Laravel 示例
class UserStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
];
}
}
// 控制器中使用
public function store(UserStoreRequest $request): JsonResponse
{
$data = $request->validated(); // 已验证过的数据
// ...
}
避免直接操作 $_GET / $_POST
始终通过框架的请求对象获取输入,便于中间件处理、单元测试和全局过滤。
不要信任用户输入
即使验证通过,在业务层也需要进一步处理(如 SQL 注入防护、XSS 过滤)。
业务逻辑分离
Service 层(推荐)
将所有业务逻辑抽取到独立的服务类中。
// 不好的做法
public function store(Request $request)
{
// 验证、数据库操作、邮件发送、日志记录全部写在这里
}
// 好的做法
public function store(UserStoreRequest $request)
{
$this->userService->createUser($request->validated());
// ...
}
Action 类(适用于复杂单一操作)
对于复杂的业务操作(如“订单结算”),可以创建单独的 Action 类。
class CheckoutAction
{
public function execute(Order $order, User $user): void
{
// 复杂的结算流程
}
}
// 控制器中调用
public function checkout(Request $request)
{
(new CheckoutAction)->execute($order, auth()->user());
}
响应输出规范
统一返回格式
- Web 视图:使用
view()或redirect(),保持视图层与控制器分离。 - API 接口:返回 JSON,包含状态码、消息、数据等。
public function show(int $id): JsonResponse { $user = UserService::find($id); return response()->json([ 'code' => 200, 'message' => 'success', 'data' => $user, ]); }
错误处理
在控制器中使用 try-catch 或通过异常处理机制统一返回错误。
public function update(int $id, Request $request): JsonResponse
{
try {
$user = UserService::update($id, $request->validated());
return $this->success($user);
} catch (NotFoundException $e) {
return $this->notFound($e->getMessage());
} catch (ValidationException $e) {
return $this->validationError($e->getErrors());
}
}
避免在控制器中直接输出 echo 或 var_dump
控制器只负责返回响应对象或视图,输出应由框架处理。
中间件与权限控制
使用中间件处理横切关注点
- 认证:
auth - 授权:
can:update,user - 限流:
throttle:60,1 - 日志:
log.requests
配置示例(Laravel)
// 在控制器构造函数中
public function __construct()
{
$this->middleware('auth');
$this->middleware('can:manage-users')->only(['store', 'update', 'destroy']);
$this->middleware('throttle:30,1')->only(['store']);
}
依赖注入
-
通过构造函数或方法参数注入依赖,避免在控制器内部硬编码。
-
存储库模式:如果需要直接操作数据库,通过依赖注入 Repository 类,而不是直接使用模型。
class UserController extends Controller { private UserService $userService; public function __construct(UserService $userService) { $this->userService = $userService; } public function index(): View { $users = $this->userService->getPaginatedList(); return view('users.index', compact('users')); } }
常见反模式(应避免)
- 上帝控制器:一个控制器包含几十个方法,处理多类资源。
- 胖控制器:控制器中包含大量 SQL 查询、业务判断、视图渲染逻辑。
- 直接操作全局变量:如
$_SESSION、$_FILES等,应通过框架封装好的对象操作。 - 返回 HTML 片段:与前端框架配合时,应使用 API 返回 JSON,而非拼接 HTML。
- 硬编码配置:如数据库连接信息、缓存时间等,应通过配置文件或环境变量管理。
实战示例(Laravel 完整资源控制器)
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\UserStoreRequest;
use App\Http\Requests\UserUpdateRequest;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
$this->middleware('auth');
$this->middleware('can:manage-users')->except(['index', 'show']);
}
public function index(Request $request): View
{
$users = $this->userService->paginate($request->all());
return view('admin.users.index', compact('users'));
}
public function store(UserStoreRequest $request): JsonResponse
{
$user = $this->userService->create($request->validated());
return response()->json([
'code' => 201,
'message' => '用户创建成功',
'data' => $user,
], 201);
}
public function show(int $id): JsonResponse
{
$user = $this->userService->findOrFail($id);
return response()->json([
'code' => 200,
'message' => 'success',
'data' => $user,
]);
}
public function update(int $id, UserUpdateRequest $request): JsonResponse
{
$user = $this->userService->update($id, $request->validated());
return response()->json([
'code' => 200,
'message' => '用户更新成功',
'data' => $user,
]);
}
public function destroy(int $id): JsonResponse
{
$this->userService->delete($id);
return response()->json([
'code' => 200,
'message' => '用户删除成功',
]);
}
}
测试可测性
- 控制器应只测试 HTTP 流程(请求、响应状态、重定向),不测试业务结果(那是 Service 层的责任)。
- 使用依赖注入使得在测试中容易模拟业务服务。
示例测试:
public function test_store_creates_user_and_redirects()
{
$this->mock(UserService::class, function ($mock) {
$mock->shouldReceive('create')->once()->andReturn(new User(['id' => 1]));
});
$response = $this->post('/users', [
'name' => 'Test',
'email' => 'test@example.com',
'password' => 'password',
]);
$response->assertRedirect(route('users.show', 1));
}
| 规范项 | 推荐做法 | 反例 |
|---|---|---|
| 方法职责 | 单一 RESTful 方法 | 一个方法处理增删改查 |
| 业务逻辑 | 委托给 Service 或 Action 类 | 直接写在控制器中 |
| 输入验证 | Form Request 类 | 在控制器中用 if 验证 |
| 响应输出 | 返回 View 或 JsonResponse 对象 |
直接 echo 或 exit |
| 依赖管理 | 构造函数或方法注入 | 静态方法或 new 实例化 |
| 错误处理 | 异常捕获 + 统一返回格式 | 使用 die() 或 var_dump() |
| 权限控制 | 中间件 | 在控制器方法中手动判断 |
遵循这些规范,你的 PHP 控制器将更加简洁、可维护、可测试,也便于团队协作和框架升级。