geeky / request-objects

为 Symfony 定制的自定义请求对象,使生活更加轻松

1.0.5 2020-06-20 00:29 UTC

This package is auto-updated.

Last update: 2024-09-17 00:23:36 UTC


README

Build Status Scrutinizer Code Quality Latest Stable Version Total Downloads License

注意:此库来自此库 fesor/request-objects,但此库正在为 symfony v5+ 优化。进行了一些更新

为什么?

Symfony 表单组件是处理表单的强大工具。但如今情况已改变。复杂表单大多在客户端处理。对于简单表单,symfony/forms 有非常大的开销。

在某些情况下,你可能根本不需要表单。例如,如果你正在开发 HTTP API,你可能只需要与请求有效负载交互。那么为什么不用一些用户定义的对象来包装请求有效负载,并仅验证它呢?这也鼓励了关注点的分离,并有助于你在 API 版本控制的情况下。

使用方法

首先,我们需要通过 composer 安装此包

composer require geeky/request-objects

并在 config/bundels.php 中注册包

    return [
     ....
     Fesor\RequestObject\Bundle\RequestObjectBundle::class => ['all' => true],
    ];

包不需要任何额外的配置,但你也可以在包配置中指定错误响应提供者服务。我们将在“处理验证错误”部分回到这一点。

定义你的请求对象

所有用户定义的请求都应该扩展 Fesor\RequestObject\RequestObject。让我们为用户注册操作创建一个简单的请求对象

use Fesor\RequestObject\RequestObject;
use Symfony\Component\Validator\Constraints as Assert;

class RegisterUserRequest extends RequestObject
{
    public function rules()
    {
        return new Assert\Collection([
            'email' => new Assert\Email(['message' => 'Please fill in valid email']),
            'password' => new Assert\Length(['min' => 4, 'minMessage' => 'Password is to short']),
            'first_name' => new Assert\NotNull(['message' => 'Please provide your first name']),
            'last_name' => new Assert\NotNull(['message' => 'Please provide your last name'])
        ]);
    }
}

然后我们可以在我们的操作中使用它

public function registerUserAction(RegisterUserRequest $request)
{
    // Do Stuff! Data is already validated!
}

此包将绑定验证后的请求对象到 $request 参数。请求对象具有非常简单的数据交互接口。它与 Symfony 的请求对象非常相似,但默认情况下被视为不可变(尽管你可以添加一些设置器,如果你愿意的话)

// returns value from payload by specific key or default value if provided
$request->get('key', 'default value');

// returns whole payload
$request->all();

有效负载从哪里来?

此库实现了 PayloadResolver 接口,其行为如下

  1. 如果请求可以有正文(即它是 POST、PUT、PATCH 或任何带有正文的请求),则它使用 $request->request->all()$request->files->all() 数组的并集作为有效负载。

  2. 如果请求不能有正文(即 GET、HEAD 动词),则它使用 $request->query->all()

如果你希望为有效负载提取应用自定义逻辑,你可以在你的请求对象中实现 PayloadResolver 接口

class CustomizedPayloadRequest extends RequestObject implements PayloadResolver
{
    public function resolvePayload(Request $request)
    {
        $query = $request->query->all();
        // turn string to array of relations
        if (isset($query['includes'])) {
            $query['includes'] = explode(',', $query['includes']);
        }

        return $query;
    }
}

这将允许你对你的请求进行一些疯狂的操作,并且可以大量地重复使用代码。

验证有效负载

如前例所示,rules 方法应该返回用于 symfony 验证器 的验证规则。你的请求有效负载将与此规则进行验证,你将在操作中获取有效数据。

如果你有一些依赖于有效负载数据的验证规则,则可以通过验证组来处理。

请注意:由于 Collection 约束验证器的限制,使用组并不那么方便。因此,建议在具有对有效负载数据依赖的复杂情况下使用 Callback 验证器。有关此问题的详细信息,请参阅 示例

你可以通过实现 validationGroup 方法来提供验证组

public function validationGroup(array $payload)
{
    return isset($payload['context']) ?
        ['Default', $payload['context']] : null;
}

处理验证错误

如果验证数据无效,库将抛出包含验证错误和请求对象的异常。

但如果你不想通过 kernel.exception 监听器来处理它,你还有几种选择。

第一种是使用你的控制器操作来处理错误

public function registerUserAction(RegisterUserRequest $request, ConstraintViolationList $errors)
{
    if (0 !== count($errors)) {
        // handle errors
    }
}

但这并不方便,如果你只是需要返回常见的错误响应,它还会破坏DRY原则。这就是为什么库为你提供了ErrorResponseProvider接口。你可以在请求对象中实现它,并将此代码移动到getErrorResponse方法中。

public function getErrorResponse(ConstraintViolationListInterface $errors)
{
    return new JsonResponse([
        'message' => 'Please check your data',
        'errors' => array_map(function (ConstraintViolation $violation) {

            return [
                'path' => $violation->getPropertyPath(),
                'message' => $violation->getMessage()
            ];
        }, iterator_to_array($errors))
    ], 400);
}

异常监听器处理

如果你需要从异常本身处理验证异常,可以查看以下示例:异常示例

因此,首先你应该创建一个src/EventListener/ExceptionListener.php文件,或者你想要的地方,然后将示例文件粘贴进去,你可以根据需要自定义响应。

其次,你必须将创建的文件注册到services.yaml文件中,如下所示

parameters:

services:
    App\EventListener\ExceptionListener: #here you can create your file anywhere else
            tags:
                - { name: kernel.event_listener, event: kernel.exception }

更多示例

如果你还不确定这对你有用,请查看examples目录以获取更多用例。没有找到你的情况?那么在问题中分享你的用例!

贡献

请随时提供反馈和功能请求或提交问题。PR受到欢迎!