arimac / laravel-request-mapper
Requires
- doctrine/annotations: ^1.13
- doctrine/cache: ^1.0
- illuminate/contracts: ^8.0
- illuminate/http: ^8.0
- illuminate/support: ^8.0
- illuminate/validation: ^8.0
- symfony/validator: ^5.4
Requires (Dev)
- orchestra/testbench: ^6.2
- phpunit/phpunit: ^9.5
README
此组件允许您将映射自请求的 DTO 对象注入到操作中。
1. 安装
您可以使用以下命令通过 composer 安装此包
composer require maksi/laravel-request-mapper
此包将自动注册自己。
2. 要求
PHP 7.1 或更高版本和 Laravel 5.5 或更高版本
3. 基本用法
3.1 创建 DTO 对象
<?php declare(strict_types = 1); use Maksi\LaravelRequestMapper\Filling\RequestData\AllRequestData; final class RoomSearchRequestData extends AllRequestData { private $name; protected function init(array $data): void { $this->name = $data['name'] ?? null; } public function getName(): string { return $this->name; } }
您的 DTO 对象应扩展其中一个 RequestData 类
RequestData 类负责 映射策略。
$data
数组在 init
中是一个 array
,它由 映射策略 类返回。 基本上 $data
是来自 Request
的某些数据。
3.2 注入到操作中
DTO 对象可以注入到任何类型的操作中。
<?php declare(strict_types = 1); /** * @package App\Http\Controller */ class RoomSearchController { ... public function __invoke(RoomSearchRequestData $payload) // DTO object injected { } }
3.3 验证 DTO 对象
您可以对 DTO 对象应用验证
- 在将数据映射到 DTO 之前(
laravel
验证) - 在将数据映射到 DTO 之后(
symfony annotation
验证)
3.3.1 应用 Laravel 验证
Laravel 验证在对象填充之前应用于 RequestData
对象。
- 您应该创建一个包含验证规则的类。此类应实现
Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\ValidationRuleInterface
接口(如果不需要更改验证messages
和customAttributes
,则可以扩展Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\AbstractValidationRule
类)
<?php declare(strict_types = 1); namespace Maksi\LaravelRequestMapper\Tests\Integration\LaravelNestedValidation\Stub; use Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\AbstractValidationRule; class ValidatorRule extends AbstractValidationRule { public function rules(): array { return [ 'nested' => 'array|required', 'title' => 'string|required', ]; } }
- 接下来,您应该将此规则应用于 DTO 对象。这应通过
annotation
完成。
<?php declare(strict_types = 1); namespace Maksi\LaravelRequestMapper\Tests\Integration\LaravelNestedValidation\Stub; use Maksi\LaravelRequestMapper\Filling\RequestData\JsonRequestData; use Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\Annotation\ValidationClass; /** * @ValidationClass(class="\Maksi\LaravelRequestMapper\Tests\Integration\LaravelNestedValidation\Stub\ValidatorRule") */ class RootRequestDataStub extends JsonRequestData { private $title; private $nested; protected function init(array $data): void { $this->title = $data['title'] ?? null; $this->nested = new NestedRequestDataStub($data['nested'] ?? []); } public function getTitle(): string { return $this->title; } public function getNested(): NestedRequestDataStub { return $this->nested; } }
string
@ValidationClass(class="\Maksi\LaravelRequestMapper\Tests\Integration\LaravelNestedValidation\Stub\ValidatorRule")
表示在填充当前 DTO 之前,应应用 \Maksi\LaravelRequestMapper\Tests\Integration\LaravelNestedValidation\Stub\ValidatorRule
规则,这些规则将注入到 dto 中。
3.3.2 应用 Symfony annotation 验证
Annotation Symfony 验证应用于 RequestData
对象中的属性(因此此验证在创建 DTO 对象后应用)。
首先,您应该将 @Type(type="annotation")
注解添加到 RequestData 对象中。之后,您可以应用验证到 DTO 对象中(有关更多信息,请参阅 symfony 验证文档)
<?php declare(strict_types = 1); namespace Maksi\LaravelRequestMapper\Tests\Integration\AnnotationValidation\Stub; use Maksi\LaravelRequestMapper\Filling\RequestData\AllRequestData; use Maksi\LaravelRequestMapper\Validation\Annotation\Type; use Symfony\Component\Validator\Constraints as Assert; /** * @Type(type="annotation") */ class AllRequestDataStub extends AllRequestData { /** * @Assert\Type(type="int") * @Assert\NotBlank() */ private $allAge; /** * @var string * @Assert\NotBlank() */ private $allTitle; protected function init(array $data): void { $this->allAge = $data['age'] ?? null; $this->allTitle = $data['title'] ?? null; } public function getAllTitle(): string { return $this->allTitle; } public function getAllAge(): int { return $this->allAge; } }
4. 嵌套对象验证
4.1. Symfony annotation 验证
您可以使用相同的方式创建嵌套 DTO 对象,例如:
根类
<?php declare(strict_types = 1); namespace Maksi\LaravelRequestMapper\Tests\Integration\AnnotationNestedValidation\Stub; use Maksi\LaravelRequestMapper\Filling\RequestData\JsonRequestData; use Maksi\LaravelRequestMapper\Validation\Annotation\Type; use Symfony\Component\Validator\Constraints as Assert; /** * @Type(type="annotation") */ class RootRequestDataStub extends JsonRequestData { /** * @Assert\NotBlank() * @Assert\Type(type="string") */ private $title; /** * @Assert\Valid() */ private $nested; // this property should have `Valid` annotation for validate nested object protected function init(array $data): void { $this->title = $data['title'] ?? null; $this->nested = new NestedRequestDataStub($data['nested'] ?? []); } public function getTitle(): string { return $this->title; } public function getNested(): NestedRequestDataStub { return $this->nested; } }
嵌套类
<?php declare(strict_types = 1); namespace Maksi\LaravelRequestMapper\Tests\Integration\AnnotationNestedValidation\Stub; use Maksi\LaravelRequestMapper\Filling\RequestData\JsonRequestData; use Symfony\Component\Validator\Constraints as Assert; class NestedRequestDataStub extends JsonRequestData { /** * @Assert\NotBlank() * @Assert\Type(type="string") */ private $nestedTitle; protected function init(array $data): void { $this->nestedTitle = $data['title'] ?? null; } public function getTitle(): string { return $this->nestedTitle; } }
4.2. 嵌套的 Laravel 验证
因此,由于 Laravel 验证在填充 RequestData
对象之前应用,因此您只需创建与无嵌套验证相同的验证类。
<?php use Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\AbstractValidationRule; class ValidatorRule extends AbstractValidationRule { /** * @return array */ public function rules(): array { return [ 'nested' => 'array|required', 'title' => 'string|required', 'nested.title' => 'string|required', // nested object validation ]; } }
5. 映射策略
默认情况下,包有 3 个策略
AllStrategy - 负责从 $request->all()
数组中填充数据。如果想要使用此策略,那么您的 RequestData
对象应扩展 AllRequestData
类。
HeaderStrategy - 负责从 $request->header->all()
数组中填充数据。如果想要使用此策略,那么您的 RequestData
对象应扩展 HeaderRequestData
类。
JsonStrategy - 负责从 $request->json()->all()
数组中填充数据。如果您想使用此策略,则您的 RequestData
对象应扩展 JsonRequestData
类。
6. 创建自定义映射策略
您可以为我们的应用程序创建自定义映射策略。
6.1 创建自定义策略
您的策略应实现 StrategyInterface;
<?php declare(strict_types = 1); namespace App\Http\RequestDataStrategy; use App\Http\RequestData\TeacherSearchRequestData; use Illuminate\Http\Request; use Maksi\LaravelRequestMapper\Filling\Strategies\StrategyInterface; use Maksi\LaravelRequestMapper\Filling\RequestData\RequestData; class TeacherSearchStrategy implements StrategyInterface { public function resolve(Request $request): array { return $request->all(); } public function support(Request $request, RequestData $object): bool { return $object instanceof TeacherSearchRequestData && $request->routeIs('teacher-search'); } }
方法 support
定义策略是否可用于 resolve
对象。此方法有两个参数 $request
和 $object
$request
作为Request
实例$object
- 它是一个空的 DTO 实例,将被填充
方法 resolve
将返回一个数组,该数组将被注入到 DTO 实例中。此方法接受 $request
对象。
6.2 为策略创建 RequestData 类
如果您想创建自己的策略,应扩展 RequestData
<?php declare(strict_types = 1); namespace App\Http\RequestData; use Maksi\LaravelRequestMapper\Filling\RequestData\RequestData; use Symfony\Component\Validator\Constraints as Assert; final class TeacherSearchRequestData extends RequestData { /** * @var string * * @Assert\NotBlank() * @Assert\Type(type="string") */ private $name; protected function init(array $data): void { $this->name = $data['name'] ?? null; } public function getName(): string { return $this->name; } }
6.3 在 ServiceProvider 中注册您的策略
您应通过 addStrategy
方法将您的 strategy
实例添加到 Maksi\LaravelRequestMapper\StrategiesHandler
<?php declare(strict_types = 1); namespace App\Http\Provider; use App\Http\RequestDataStrategy\TeacherSearchStrategy; use Illuminate\Support\ServiceProvider; use Maksi\LaravelRequestMapper\FillingChainProcessor; /** * Class RequestMapperProvider * * @package App\Http\Provider */ class RequestMapperProvider extends ServiceProvider { /** * @param FillingChainProcessor $fillingChainProcessor */ public function boot(FillingChainProcessor $fillingChainProcessor): void { $fillingChainProcessor->addStrategy($this->app->make(TeacherSearchStrategy::class)); } }
7. 修改验证异常
- 创建一个扩展
\Maksi\LaravelRequestMapper\Validation\ResponseException\AbstractException
并实现 toResponse 方法的 Exception
例如
<?php class StringException extends \Maksi\LaravelRequestMapper\Validation\ResponseException\AbstractException implements \Illuminate\Contracts\Support\Responsable { /** * Create an HTTP response that represents the object. * * @param \Illuminate\Http\Request $request * * @return \Illuminate\Http\JsonResponse */ public function toResponse($request) { return \Illuminate\Http\JsonResponse::create('Invalid data provided') ->setStatusCode(\Illuminate\Http\Response::HTTP_UNPROCESSABLE_ENTITY); } }
- 在
config/laravel-request-mapper.php
中定义exception-class
键
<?php declare(strict_types = 1); return [ 'exception-class' => \Maksi\LaravelRequestMapper\Validation\ResponseException\DefaultException::class, ];
8. 项目示例
您可以在 https://github.com/E-ZSTU/rozklad-rest-api 项目中看到此包的使用示例。
贡献
有关详细信息,请参阅 CONTRIBUTING
许可证
MIT 许可证 (MIT)。有关更多信息,请参阅 许可证 文件
待办事项
- 考虑 https://www.reddit.com/r/laravel/comments/af843q/laravel_request_mapper/edx39cj
- 考虑 https://www.reddit.com/r/laravel/comments/af843q/laravel_request_mapper/edx8mci
- 删除 symfony 验证,因为我不确定 Laravel 社区是否需要它
- 为
change exception
添加集成测试 - 为策略添加优先级
- 如何从中间件获取此 DTO(只需将
RequestData
注册为单例即可)