arimac / laravel-request-mapper

1.1.1 2022-02-02 12:37 UTC

This package is not auto-updated.

Last update: 2024-09-26 02:00:54 UTC


README

Build Status Code Coverage Latest Stable Version License

此组件允许您将映射自请求的 DTO 对象注入到操作中。

  1. 安装
  2. 要求
  3. 基本用法
  4. 嵌套对象
  5. 映射策略
  6. 创建自定义映射策略
  7. 如何创建自定义异常?
  8. 项目示例
  9. 贡献
  10. 许可证
  11. 待办事项

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 对象。

  1. 您应该创建一个包含验证规则的类。此类应实现 Maksi\LaravelRequestMapper\Validation\BeforeType\Laravel\ValidationRuleInterface 接口(如果不需要更改验证 messagescustomAttributes,则可以扩展 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',
        ];
    }
}
  1. 接下来,您应该将此规则应用于 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. 修改验证异常

  1. 创建一个扩展 \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);
    }
}
  1. 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)。有关更多信息,请参阅 许可证 文件

待办事项