首页 > 解决方案 > 在非规范化嵌入关系时如何不允许 IRI?

问题描述

我有一个客户实体,它在一个可以为空的 OneToOne 关系中链接到一个联系人实体。

当我创建新客户时,链接联系人的创建是可选的,但不能填写现有联系人的 IRI。换句话说,它必须是一个新的联系人,否则什么都不是。

class Customer
{

    #[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
    #[Groups([
        'write:Customer:collection', '...'
    ])]
    private $contact;
}

'write:Customer:collection'规范化组也存在于 Contact 属性中。

通过以下良好的请求,我可以创建我的客户和我的联系人,这没有问题。

{
    "name": "test company",
    "contact": [
        "firstname" => 'hello',
        "lastname" => 'world'
    ]
}

问题:

但是,我不想要它,我也可以使用现有联系人创建新客户,如下所示:

{
    "name": "test company",
    "contact": "/api/contacts/{id}"
}

序列化文档中所述:

非规范化嵌入关系时适用以下规则:

  • 如果嵌入资源中存在 @id 键,则将通过数据提供程序检索与给定 URI 对应的对象。嵌入关系中的任何更改也将应用于该对象。
  • 如果不存在 @id 键,将创建一个新对象,其中包含嵌入 JSON 文档中提供的数据。

但是,如果存在 @id 键,我想禁用特定验证组的规则。

我想创建一个自定义约束来检查数据库中是否存在资源,但令我惊讶的是没有约束允许检查这一点。

我错过了什么吗?你有我的解决方案吗?提前致谢。

标签: embedded-resourceapi-platform.comdenormalization

解决方案


我终于创建了一个自定义约束来检查请求中发送的嵌入资源是否已经由 Doctrine 管理。

约束本身:

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 */
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AcceptPersisted extends Constraint
{
    public bool $expected = false;
    public string $mustBePersistMessage = 'Set a new {{ entity }} is invalid. Must be an existing one.';
    public string $mustBeNotPersistMessage = 'Set an existing {{ entity }} is invalid. Must be a new one.';

    public function __construct(bool $expected = false, $options = null, array $groups = null, $payload = null)
    {
        parent::__construct($options, $groups, $payload);
        $this->expected = $expected;
    }
}

它验证器:

namespace App\Validator\Constraints;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class AcceptPersistedValidator extends ConstraintValidator
{
    public function __construct(private EntityManagerInterface $entityManager) {}

    public function validate($value, Constraint $constraint)
    {
        if (!$constraint instanceof AcceptPersisted) {
            throw new UnexpectedTypeException($constraint, AcceptPersisted::class);
        }

        if ($value === null) {
            return;
        }

        //if current value is/is not manage by doctrine
        if ($this->entityManager->contains($value) !== $constraint->expected) {
            $entity = (new \ReflectionClass($value))->getShortName();
            $message = $constraint->expected ? $constraint->mustBePersistMessage : $constraint->mustBeNotPersistMessage;

            $this->context->buildViolation($message)->setParameter("{{ entity }}", $entity)->addViolation();
        }
    }
}

所以,我只需要在我的属性上添加自定义约束:

use App\Validator\Constraints as CustomAssert;

class Customer
{

    #[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
    #[CustomAssert\AcceptPersisted(expected: false)]
    //...
    private $contact;
}

推荐阅读