反规范化嵌入关系时如何不允许 IRI?

How not to allow an IRI when denormalizing embedded relations?

我有一个链接到联系人实体的客户实体,处于可为空的一对一关系中。

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

class Customer
{

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

The 'write:Customer:collection' denormalization group is also present on the Contact properties.

有了如下好的请求,我可以创建我的客户和我的联系人,没问题。

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

问题:

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

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

serialization documentation 中所述:

The following rules apply when denormalizing embedded relations:

  • If an @id key is present in the embedded resource, then the object corresponding to the given URI will be retrieved through the data provider. Any changes in the embedded relation will also be applied to that object.
  • If no @id key exists, a new object will be created containing data provided in the embedded JSON document.

但是,如果存在 @id 密钥,我想针对特定验证组禁用该规则。

我想创建一个自定义约束来检查数据库中是否不存在该资源,但令我惊讶的是没有任何约束允许对此进行检查。

我错过了什么吗?你有解决办法吗?提前致谢。

我终于创建了一个自定义约束,用于检查请求中发送的嵌入资源是否已由 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;
}