baptiste-contreras / symfony-request-param-bundle
Symfony 包,可使用对象请求参数
Requires
- php: >=8.1
- sensio/framework-extra-bundle: ~4.4 || ^5.0 || ^6.0
- symfony/config: ~4.4 || ^5.0 || ^6.0
- symfony/dependency-injection: ~4.4 || ^5.0 || ^6.0
- symfony/http-kernel: ~4.4 || ^5.0 || ^6.0
- symfony/serializer: ~4.4 || ^5.0 || ^6.0
- symfony/serializer-pack: ^v1.1.0
- symfony/validator: ~4.4 || ^5.0 || ^6.0
Requires (Dev)
Conflicts
- ext-swoole: <4.6.0
This package is auto-updated.
Last update: 2024-09-10 00:28:22 UTC
README
目标
本包旨在在PHP的Symfony中重现Java Spring的 Request Param注解。
使用此包,您可以使用PHP 8.1的本地属性来获得所需的结果
#[Route('/demo', name: 'demo_')] class RegisterController extends AbstractApiController { #[Route(path: '/{uid}', name: 'register', methods: ['POST'])] #[AutoProvideRequestDto] public function register(#[DtoRequestParam] RegisterRequest $registerRequest, ?string $uid = null): Response { dd($registerRequest); } }
在我们的例子中,$registerRequest
对象将使用请求中的数据构建并验证。
安装
composer require baptiste-contreras/symfony-request-param-bundle
DtoRequestParam 参数
为 DtoRequestParam 提供了几个参数,并允许您修改DTO注入的行为。
- sourceType
- throwDeserializationException
- validateDto
- validationGroups
sourceType
- string sourceType. 默认值 SourceType::JSON。这允许您指示输入数据类型。
更改此值时,必须确保存在一个可以支持该类型sourceType的 DtoProviderDriverInterface。否则,您将收到一个 NoDtoProviderDriverFoundException。
此文档中稍后将对打包的sourceType进行详细说明。
示例
#[Route('/demo', name: 'demo_')] class RegisterController extends AbstractApiController { #[Route(path: '/', name: 'register', methods: ['POST'])] #[AutoProvideRequestDto] public function register(#[DtoRequestParam(sourceType: SourceType::JSON)] RegisterRequest $registerRequest): Response { dd($registerRequest); } #[Route(path: '/xml', name: 'register_xml', methods: ['POST'])] #[AutoProvideRequestDto] public function registerXml(#[DtoRequestParam(sourceType: 'xml')] RegisterRequest $registerRequest): Response { dd($registerRequest); } }
throwDeserializationException
- bool throwDeserializationException. 默认值 true。
如果为 true,则在反序列化阶段发生的任何异常都不会被捕获,并将重新抛出。如果您将该参数设置为 false,则反序列化过程中发生的异常将被捕获、记录,并将 null 注入到DTO中。
示例
#[Route('/demo', name: 'demo_')] class RegisterController extends AbstractApiController { #[Route(path: '/', name: 'register', methods: ['POST'])] #[AutoProvideRequestDto] public function register(#[DtoRequestParam(throwValidationException: true)] RegisterRequest $registerRequest): Response { // If something went bad during the deserialization, the exception is rethrown and this code will not be called... dd($registerRequest); } #[Route(path: '/test2', name: 'test2', methods: ['POST'])] #[AutoProvideRequestDto] public function test2(#[DtoRequestParam(throwValidationException: false)] ?RegisterRequest $registerRequest): Response { // Notice the type difference with the first method, we add "?RegisterRequest" because $registerRequest // will be null if there is a problem during the deserialization. dd($registerRequest); } }
validateDto
- bool validateDto. 默认值 true。
如果为 true,则将执行验证阶段,使用 Symfony的验证器。如果存在约束违反情况,则包将抛出自定义异常并处理错误格式化和显示(稍后详细介绍)。
如果为 false,则不会进行验证,您的DTO将在反序列化后直接注入到控制器的方法中。
要设置您的验证约束,您可以使用官方的 Symfony文档,但这里有一些简要说明。
final class RegisterRequest { #[NotBlank] private ?string $name = null; #[Positive] private ?int $age = null; // getters, setters, ... }
示例
#[Route('/demo', name: 'demo_')] class RegisterController extends AbstractApiController { #[Route(path: '/', name: 'register', methods: ['POST'])] #[AutoProvideRequestDto] public function register(#[DtoRequestParam(validateDto: true)] RegisterRequest $registerRequest): Response { dd($registerRequest); // My DTO is validated } #[Route(path: '/test2', name: 'test2', methods: ['POST'])] #[AutoProvideRequestDto] public function registerXml(#[DtoRequestParam(validateDto: false)] RegisterRequest $registerRequest): Response { dd($registerRequest); // No validation } }
validationGroups
- array|string validationGroups. 默认值 ['Default']。
由于此包内部使用Symfony的验证器,我们可以指定验证组以仅验证约束子集。您可以在这里了解更多信息。
您可以传递单个字符串,表示一个验证组,或者如果您想使用多个验证组,则可以传递字符串数组。
请注意,如果 validateDto 为 true,则不能传递空数组或空字符串 ([] 或 ''),否则您将收到一个 EmptyValidationGroupsException。
示例
#[Route('/demo', name: 'demo_')] class RegisterController extends AbstractApiController { #[Route(path: '/', name: 'register', methods: ['POST'])] #[AutoProvideRequestDto] public function register(#[DtoRequestParam(validationGroups: 'register-validation-1')] RegisterRequest $registerRequest): Response { dd($registerRequest); } #[Route(path: '/test2', name: 'test2', methods: ['POST'])] #[AutoProvideRequestDto] public function registerXml(#[DtoRequestParam(validationGroups: ['register-validation-1', 'register-validation-2'])] RegisterRequest $registerRequest): Response { dd($registerRequest); } }
可用来源
目前仅支持 json 来源。您可以通过创建一个针对您需求的 DtoProviderDriverInterface 来扩展此包。
当您的自定义提供者的 supports 方法被调用并返回 true 时,它将被选中用于反序列化。
以下是一个XML提供者的示例。
class CustomXmlProvider implements DtoProviderDriverInterface { public function __construct(private readonly SerializerInterface $serializer) { } public function fromRequest(DtoProviderContext $context, Request $request): mixed { try { // The Symfony's serialize is used here but feel free to handle the raw data your way ! return $this->serializer->deserialize($request->getContent(), $context->getDtoClass(), 'xml', []); } catch (\Throwable $exception) { // This is optional, but you should do it, otherwise the // throwDeserializationException parameter will be useless... if ($context->shouldThrowDeserializationException()) { throw $exception; } return null; } } public function supports(DtoProviderContext $dtoProviderContext): bool { return 'xml' === $dtoProviderContext->getSourceType(); // You can add more logic if needed } }
如果您想确保它首先被调用,您可以调整您自定义提供者的标签优先级。有关文档,请在此处查看。
request_param.dto-provider-driver 是与 DtoProviderDriverInterface 相关的标签。
以下是一个修改自定义提供者优先级的示例
# services.yaml App\CustomProvider: class: 'App\CustomProvider' tags: - { name: "request_param.dto-provider-driver", priority: 20 }
JsonDtoProvider
如果您指定 SourceType::JSON 作为源类型,将使用 JsonDtoProvider 服务。
内部使用 Symfony 的序列化器
目前使用一个非常基本的序列化器配置,并使用设置器填充您的 DTO。
稍后您将能够更改这一点。(实际上,您可以通过配置序列化器组件来更改它,更多信息请在此处查看)
错误处理
展示者
错误展示者是负责以预定义格式返回响应的服务。例如 JSON 错误格式
{ "error": true, "status": 400, "message": "Bad request" }
如果您想添加自己的错误展示者,该展示者返回自定义格式,只需创建一个实现 ErrorPresenterDriverInterface 的对象即可。
像上面的自定义提供者一样,不需要采取任何进一步的操作(即,如果您的自定义错误展示者的 supports 方法返回 true,它将被使用!)。
您必须意识到,首先注册的错误处理器,其返回值为 true,将被使用。对于提供者而言,您可以通过标签优先级来调整您的处理器在选择链中的位置,以下是相关文档。
request_param.error-presenter-driver 是与 ErrorPresenterDriverInterface 相关的标签。
以下是一个修改自定义提供者优先级的示例
# services.yaml App\CustomErrorPresenter: class: 'App\CustomErrorPresenter' tags: - { name: "request_param.error-presenter-driver", priority: 20 }
当您创建自己的展示者时,除了 supports 方法外,您必须实现两个重要方法
- presentBadRequest:在验证异常或 HTTP 400 错误时被调用
- presentTechnicalError:在任何其他情况下被调用
以下是一个自定义基本 HTML 错误展示者的示例
class BasicHtmlErrorPresenter implements ErrorPresenterDriverInterface { public function presentBadRequest(RequestDtoException $requestDtoException, Request $request): Response { return new Response('<html><body><h1>Bad request</h1></body></html>', 400); } public function presentTechnicalError(RequestDtoException $requestDtoException, Request $request): Response { return return new Response('<html><body><h1>Technical error</h1></body></html>', 500); } public function supports(RequestDtoException $requestDtoException, Request $request): bool { return $request->headers->has('....'); // Your logic here } }
JsonErrorPresenter
默认情况下,JsonErrorPresenter 服务将用于返回包含错误详细信息的 JSON 响应。
以下是两个响应示例
- 给定一个无效请求,它将产生
{ "error": true, "success": false, "message": "Bad request", "status": 400, "errors": [ "[property_1] : Should not be blank" ] }
- 给定任何其他错误,结果将是
{ "error": true, "success": false, "message": "Technical error", "status": 500 }
您可以轻松地修改此响应格式。实际上,此展示者使用装饰器堆栈来创建响应数组。
JsonErrorPresenter::presentBadRequest 使用 stack_response_formatter_json_bad_request 堆栈。JsonErrorPresenter::presentTechnicalError 使用 stack_response_formatter_json_technical_error 堆栈。
上述两个堆栈都是一系列 JsonFormatterInterface 逐个应用的。
让我们看看 stack_response_formatter_json_bad_request 的定义
<stack id="stack_response_formatter_json_bad_request"> <service parent="stack_response_formatter_json_bad_request_default"/> <!-- It's a little trick to easily append a new formatter to the defaults defined in stack_response_formatter_json_bad_request_default --> </stack> <stack id="stack_response_formatter_json_bad_request_default"> <service alias="request_param.response.formatter.json.validation" /> <service alias="request_param.response.formatter.json.default" /> </stack>
让我们创建一个新的格式化器来添加一个 "test": "ok"
键,并假设移除 "success": false
键
class CustomJsonFormatter implements JsonFormatterInterface { // $this->decorated is the next formatter in the chain (i.e. the one we decorate with our custom formatter) // It can be null if our formatter is the last to be called // the order depends on the stack definition you made in your services.yaml public function __construct(private readonly ?JsonFormatterInterface $decorated = null) { } public function format(array $currentResponse, RequestDtoException $requestDtoException, Request $request, string $defaultMessage, int $httpCode): array { $currentResponse['test'] = 'ok'; if ($this->decorated) { $currentResponse = $this->decorated->format( $currentResponse, $requestDtoException, $request, $defaultMessage, $httpCode ); unset($currentResponse['success']); } return $currentResponse; } }
我们需要在 services.yaml 中添加一些额外的配置
App\CustomJsonFormatter: class: 'App\CustomJsonFormatter' stack_response_formatter_json_bad_request: stack: - App\CustomJsonFormatter: ~ - alias: stack_response_formatter_json_bad_request_default # In this configuration, our formatter is the first in the chain, and we include the default chain # stack_response_formatter_json_bad_request_default is an alias for request_param.response.formatter.json.validation AND request_param.response.formatter.json.default
然后就是
{ "test": "ok", "error": true, "success": false, "message": "Bad request", "errors": [ "[property_1] : Should not be blank" ] }
使用这种装饰器方法,您真的可以很容易地自定义 JSON 响应
App\CustomJsonFormatter: class: 'App\CustomJsonFormatter' stack_response_formatter_json_bad_request: stack: - App\CustomJsonFormatter: ~ - alias: request_param.response.formatter.json.validation # In this configuration, our formatter is the first in the chain, and we only include the "validation formatter"
将产生
{ "test": "ok", "errors": [ "[property_1] : Should not be blank" ] }
默认情况下,request_param.response.formatter.json.validation 负责上面的示例中的 errors 键,而 request_param.response.formatter.json.default 负责其他键。