maksi / laravel-request-mapper
Requires
- doctrine/annotations: ^1.6
- doctrine/cache: ^1.8
- illuminate/contracts: ^5.5
- illuminate/http: ^5.5
- illuminate/support: ^5.5
- illuminate/validation: ^5.5
- symfony/validator: ^4.1
Requires (Dev)
- orchestra/testbench: ^3.7
- phpunit/phpunit: ^7.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类负责映射策略。
在init
中的$data
数组是一个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验证
在对象填充之前,为RequestData
对象应用laravel验证。
- 您应该创建一个包含验证规则的类。这个类应该实现
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的data
。
3.3.2 应用symfony annotation验证
在RequestData
对象的属性上应用symfony annotation验证(所以,在创建DTO对象之后应用此验证)
首先,您应该在RequestData对象上添加@Type(type="annotation")
注释。之后,您可以将验证应用到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 方法的异常。
例如
<?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
注册为单例)