Slim 4结合PHP-DI:如何将DTO作为控制器方法参数注入?
Slim Framework v4 中请求DTO注入控制器的最佳实践
核心问题解决思路
Slim v4 默认不会将 ServerRequestInterface 实例存入DI容器,导致直接注入DTO时容器无法解析依赖。解决关键是通过中间件将当前请求注入容器,同时为DTO配置工厂逻辑实现自动实例化与参数映射、验证。
步骤1:实现带验证的DTO类
DTO负责从请求中提取、映射参数,并做合法性校验,示例如下:
use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Constraints as Assert; class BaseDto { #[Assert\NotBlank(message: 'name不能为空')] #[Assert\Length(min: 2, max: 20, message: 'name长度需在2-20字符之间')] public string $name; #[Assert\GreaterThanOrEqual(value: 18, message: '年龄必须大于等于18')] public int $age; public function __construct(ServerRequestInterface $request) { // 合并GET与POST参数 $requestData = array_merge( $request->getQueryParams(), $request->getParsedBody() ?? [] ); // 映射参数到DTO属性 $this->name = $requestData['name'] ?? ''; $this->age = (int)($requestData['age'] ?? 0); // 执行验证 $this->validate(); } private function validate(): void { $validator = Validation::createValidator(); $violations = $validator->validate($this); if (count($violations) > 0) { $errors = []; foreach ($violations as $violation) { $errors[] = $violation->getMessage(); } throw new \RuntimeException(implode('; ', $errors)); } } }
注:这里使用
symfony/validator做验证,需先通过Composer安装:composer require symfony/validator
步骤2:添加请求注入中间件
创建中间件将当前请求存入DI容器,让容器能够获取ServerRequestInterface实例:
use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Psr7\Response; class RequestInjectorMiddleware { public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response { /** @var ContainerInterface $container */ $container = $request->getAttribute('container'); // 将当前请求存入容器 $container->set(ServerRequestInterface::class, $request); return $handler->handle($request); } }
在Slim应用中注册该中间件:
$app->add(RequestInjectorMiddleware::class);
步骤3:配置DI容器解析DTO
在php-di的容器定义中,为BaseDto注册工厂逻辑,让容器自动从请求实例化DTO:
use DI\ContainerBuilder; use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; $containerBuilder = new ContainerBuilder(); $containerBuilder->addDefinitions([ BaseDto::class => function (ContainerInterface $container) { $request = $container->get(ServerRequestInterface::class); return new BaseDto($request); }, ]); $container = $containerBuilder->build(); $app = Slim\Factory\AppFactory::createFromContainer($container);
步骤4:控制器直接注入DTO
现在可以在控制器方法中直接声明DTO参数,容器会自动完成注入:
use Psr\Http\Message\ResponseInterface; class TestController { public function index(BaseDto $myDto, ResponseInterface $response): ResponseInterface { // 直接使用DTO中的验证后参数 $responseData = [ 'status' => 'success', 'data' => [ 'name' => $myDto->name, 'age' => $myDto->age ] ]; $response->getBody()->write(json_encode($responseData)); return $response->withHeader('Content-Type', 'application/json'); } }
额外优化:全局异常处理
为了统一处理DTO验证失败的异常,添加全局异常中间件返回标准化错误响应:
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Psr7\Response; class ExceptionHandlerMiddleware { public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response { try { return $handler->handle($request); } catch (\RuntimeException $e) { $response = new Response(); $response->getBody()->write(json_encode([ 'status' => 'error', 'message' => $e->getMessage() ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } } }
注册到应用中(注意要放在其他中间件之前):
$app->add(ExceptionHandlerMiddleware::class);
内容的提问来源于stack exchange,提问作者CRaSH3k
相关产品推荐
相关产品推荐

