Symfony 7 MapRequestPayload 使用私有构造函数进行验证

问题描述 投票:0回答:1

我用 Symfony 7 设置了一个小演示项目,以评估 MapRequestPayload 的可能性并遇到问题,我似乎错过了一些使用命名构造函数的东西。

我的控制器:

<?php

declare(strict_types=1);

namespace App\Controller;

use App\DependencyInjection\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;

final readonly class HelloWordController
{
    #[Route('/')]
    public function helloWord(#[MapRequestPayload('json')] Request $request): Response
    {
        return new JsonResponse(['email' => $request->email->asString()]);
    }
}

我的请求 DTO:

<?php

declare(strict_types=1);

namespace App\DependencyInjection;

use App\DependencyInjection\Email;
use Symfony\Component\Validator\Constraints as Assert;

final class Request
{

    #[Assert\Valid] public Email $email;
    public function __construct(
        string $email,
    ){
        $this->email = EmailV6::fromString($email);
    }
}

我的值对象:

<?php

declare(strict_types=1);

namespace App\DependencyInjection;
use App\DependencyInjection\IsEmail;

final class Email
{
    private string $email;
    private function __construct(string $email)
    {
        $this->email = $email;
    }

    public function asString(): string
    {
        return $this->email;
    }

    public static function fromString(#[IsEmail] string $email): self
    {
        return new self($email);
    }
}

约束:

<?php

declare(strict_types=1);

namespace App\DependencyInjection;

use Attribute;
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;

#[Attribute]
final class IsEmail extends Constraint
{
    #[HasNamedArguments]
    public function __construct(
        array       $groups = null,
        mixed       $payload = null,
        public bool $isNullable = false,
    )
    {
        parent::__construct([], $groups, $payload);
    }
}

验证者:

<?php

declare(strict_types=1);

namespace App\DependencyInjection;

use InvalidArgumentException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

final class IsEmailValidator extends ConstraintValidator
{
    public function validate(mixed $value, Constraint $constraint): void
    {
        if (!$constraint instanceof IsEmail) {
            throw new UnexpectedTypeException($constraint, IsEmail::class);
        }

        if ($value === null && $constraint->isNullable) {
            return;
        }
        
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                sprintf(
                    '"%s" is not a valid email address',
                    $value
                )
            );
        }
    }
}

我的问题是,如果我尝试这种方式,则永远不会检查电子邮件约束。

如果我将构造函数公开并将内容放入其中,它会起作用,但不符合我们的要求:

<?php

declare(strict_types=1);

namespace App\DependencyInjection;
use App\DependencyInjection\IsEmail;

final readonly class Email
{
    public function __construct(#[IsEmail] public string $email)
    {}
}

有人可以告诉我,在没有公共构造函数的情况下我错过了什么吗?

php symfony
1个回答
0
投票

这并非不可能,因为 MapRequestPayload 上的 Symfony 使用 Symfony 序列化器/规范化器,并尝试将有效负载数据反序列化为对象。欲了解更多信息,请参阅

\Symfony\Component\Serializer\Normalizer\ObjectNormalizer

  1. 默认情况下,序列化器尝试递归地反序列化数据。
  2. 如果对象有构造函数,symfony 尝试通过构造函数创建对象。如果对象没有构造函数,symfony 会尝试获取公共属性。

但是,在您的对象

Request
中,
email
也出现在构造函数和属性中。结果,Symfony 不知道什么具有高优先级(需要查看代码,在这种情况下 symfony 会做什么)。

在这种情况下更好 - 为电子邮件对象创建自定义规范化器并在系统中注册此规范化器。

namespace Acme;

use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class EmailNormalizer implements DenormalizerInterface
{
    public function getSupportedTypes(?string $format): array
    {
        return [
            Email::class => true,
        ];
    }

    public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): ?Email
    {
        return $data ? Email::fromString($data) : null;
    }

    public function supportsDenormalization(mixed $data, string $type, ?string $format = null): bool
    {
        return \is_a($data, Email::class, true);
    }
}

在此之后,Symfony 总是调用这个反规范化器来反规范化

Email
对象。

© www.soinside.com 2019 - 2024. All rights reserved.