我用 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)
{}
}
有人可以告诉我,在没有公共构造函数的情况下我错过了什么吗?
这并非不可能,因为 MapRequestPayload 上的 Symfony 使用 Symfony 序列化器/规范化器,并尝试将有效负载数据反序列化为对象。欲了解更多信息,请参阅
\Symfony\Component\Serializer\Normalizer\ObjectNormalizer
。
但是,在您的对象
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
对象。