方案一:路由层拦截(推荐,从源头防止)
最彻底的方式是在路由定义中,使用正则表达式严格限定参数格式,使不符合规则的路由直接无法匹配,从而触发您已配置的 404 回退路由 (Route::fallback)。
// 在 config/route.php 中的路由定义
use Webman\Route;
// 原始定义:/user/123 和 /user/abc 都能匹配,但后者进入控制器会报错
// Route::any('/user/{id}', [app\controller\UserController::class, 'show']);
// 优化定义:使用 \d+ 正则,仅匹配数字,不匹配的URL会直接走fallback路由
Route::any('/user/{id:\d+}', [app\controller\UserController::class, 'show']);
优点:问题在路由匹配阶段就被解决,不会进入控制器,效率最高,最符合Webman的路由设计哲学。
fallback路由如下,应放在route.php最下方:
Route::fallback(function (Request $request) {
// 直接返回 404 视图
return response(view('404'), 404);
// 或者,如果您的项目是 API 接口,返回 JSON 格式的 404 信息
// return json(['code' => 404, 'msg' => 'Not Found'], 404);
});
方案二:控制器内校验(灵活,逻辑自控)
如果某些参数无法用简单正则描述,可以在控制器方法内部进行类型判断,手动返回 404 响应。
// 在您的控制器方法中
namespace app\controller;
use support\Request;
use support\Response;
class UserController
{
public function show(Request $request, $id): Response
{
// 校验参数是否为数字
if (!is_numeric($id)) {
// 直接返回404视图,或者重定向到404路由
return response(view('404'), 404);
}
// 正常的业务逻辑...
$user = User::find($id);
if (!$user) {
// 资源不存在,同样返回404
return response(view('404'), 404);
}
// ... 其他逻辑
}
}
优点:逻辑清晰,可以处理更复杂的校验规则,并且能轻松处理“资源是否存在”这类业务逻辑错误。
方案三:全局异常处理(兜底方案)
创建一个自定义的异常处理器,捕获由于参数类型错误引发的 TypeError等异常,并统一返回 404 页面。
- 创建异常处理器,例如
app/exception/Handler.php:
<?php
namespace app\exception;
use Throwable;
use Webman\Http\Request;
use Webman\Http\Response;
class Handler extends \support\exception\Handler
{
public function render(Request $request, Throwable $exception): Response
{
// 1. 检查是否是参数类型错误
if ($exception instanceof \TypeError) {
// 可以进一步判断错误信息是否与控制器参数相关
if (str_contains($exception->getMessage(), 'Argument')) {
// 记录日志(可选)
// Log::error("参数类型错误: " . $exception->getMessage());
// 统一返回404页面
return response(view('404', ['msg' => '请求的参数不正确']), 404);
}
}
// 2. 其他类型的异常交给父类处理
return parent::render($request, $exception);
}
}
- 注册异常处理器,在
config/exception.php中配置:
<?php
return [
'' => \app\exception\Handler::class, // 将默认异常处理指向您自定义的类
];
优点:作为兜底方案,能捕获未被预料到的类型错误,保证用户体验的统一。缺点:它处理的是“后果”而非“原因”,可能掩盖一些开发阶段需要暴露的编码错误。
方案四:使用参数验证插件(工程化方案)
社区提供了一些强大的参数验证插件(如 wekyun/tool),它们集成了完善的验证规则,可以在请求进入控制器前完成校验,验证失败时可方便地抛出异常,然后由您自定义的异常处理器接管并跳转404。
// 示例性代码,具体使用请参考插件文档
use Wekyun\Tool\Req;
class UserController {
public function show(Req $request, $id) {
// 插件内部会完成类型校验,失败则抛出验证异常
$validatedId = $request->checkGet('rule_name', ['id' => 'require|number']);
// ... 后续逻辑
}
}
优点:功能强大,规则丰富,适合大型项目需要统一、规范参数校验的场景。
如何选择?
| 方案 | 适用场景 | 优点 |
|---|---|---|
| 方案一:路由拦截 | 参数规则简单明了(如ID必为数字) | 性能最佳,从请求源头解决问题 |
| 方案二:控制器校验 | 需要复杂校验逻辑或需查询数据库验证 | 灵活性最高,控制力强 |
| 方案三:异常处理 | 作为最终兜底,防止未预料错误影响用户体验 | 保证页面统一,增强鲁棒性 |
| 方案四:验证插件 | 中大型项目,需要统一、高效的参数验证规范 | 功能全面,减少重复代码,提升开发效率 |