如何使用symfony/validator验证不可更新的列?

How to validate a not updatable column with symfony/validator?

提问人:Naico04 提问时间:9/12/2023 更新时间:9/13/2023 访问量:75

问:

我有一列,它是 DateTimeImmutable,可在用户加入平台时保存。我正在进行 phpunit 测试,我想验证列是否无法更新。我想在到达 SQL 错误之前检测错误(尝试更新列时出错)。

 #[ORM\Column(updatable: false)]
    #[Assert\Type(
        type: 'object',
        message: 'The value {{ value }} is not a valid {{ type }}.'
    )]
    private ?\DateTimeImmutable $joinedAt = null;

我尝试更新实体列,然后调用 $manager->persist($entity) 以查看当时是否存在错误,但没有任何反应。

  public function testInvalidJoinedAt(): void
    {
        $manager = static::getContainer()->get(EntityManagerInterface::class);

        // User joinedAt cannot be updated
        $now = new DateTimeImmutable();
        $existingUser = $this->getRepository(UserRepository::class)->find(0);
        $existingUser->setJoinedAt($now);

        try {
            $manager->persist($existingUser);
        } catch (\Throwable $err) {
            $this->assertEquals('???', $err->getMessage());
        }
    }

像“#[Assert\NotUpdatable]”这样的断言类型将是完美的解决方案,但事实并非如此。

PHP symfony 验证 学说 phpunit

评论

0赞 Markus Zeller 9/13/2023
这回答了你的问题吗?如何向学说实体添加只读属性?

答:

2赞 Chafik Kerboute 9/13/2023 #1

您可以创建自己的自定义验证器,查看此处的文档,在此处输入链接描述,在您的 src/Validator/Constraints 中添加文件 1)

class fileName extends Constraint
{
    public $message = 'Error Message Goes here for not modifd property';
}
 class FileName extends ConstraintValidator
 {
     public function validate($value, Constraint $constraint)
     {
         if ($value !== null) {
         $this->context->buildViolation($constraint->message)->addViolation();
     }
   }
 }
  1. 您可以将自定义验证程序添加到实体断言中

    /**
      * @ORM\Column(updatable=false)
      * @customValidator\validatorName
      */
     private ?\DateTimeImmutable $joinedAt = null;
    
-1赞 Bademeister 9/13/2023 #2

Doctrine 不验证 Symfony Validation 属性。在保存实体之前,您必须使用Symfony验证器对其进行验证。

使用自定义验证器,您需要知道它是插入还是更新,因为 Symfony 验证器不知道您是在进行插入还是更新。这样可以创建这样的自定义验证器。但这是一个更大的努力。

我会根据你的方法解决它,所以你不必验证。testInvalidJoinedAt()

namespace App\Entity;

use Doctrine\ORM\Mapping\Column;

class Entity
{
    #[Column(updatable: false)]
    private ?\DateTimeImmutable $joinedAt = null;

    public function getJoinedAt(): ?\DateTimeImmutable
    {
        return $this->joinedAt;
    }

    public function setJoinedAt(?\DateTimeImmutable $joinedAt): void
    {
        if (!$this->joinedAt instanceof \DateTimeImmutable) {
            $this->joinedAt = $joinedAt;
        }
    }
}

示例 UnitTest

class EntityTest extends TestCase
{
    public function testNewEntity() {
        $dateTime = new DateTimeImmutable();
        $entity = new Entity();
        $entity->setJoinedAt($dateTime);
        $this->assertEquals($dateTime, $entity->getJoinedAt());
    }

    public function testEntityFromDatabase() {
        // Mock entity from database
        $dateTime = new DateTimeImmutable();
        $dateTime->setDate(2022, 9, 17)->setTime(19, 31, 41);
        $entityFromDatabase = new Entity();
        $entityFromDatabase->setJoinedAt($dateTime);

        // Set joinedAt update
        $entityFromDatabase->setJoinedAt(
            (new DateTimeImmutable())->setDate(2023, 10, 19)->setTime(8, 11, 15)
        );

        $this->assertEquals($dateTime, $entityFromDatabase->getJoinedAt());
    }
}

评论

0赞 Naico04 9/13/2023
我认为这是有效的,但这并不完全是我要搜索的。谢谢你的回答!
1赞 kasali 9/13/2023 #3

Symfony Validator没有像#[Assert\NotUpdatable]这样的内置约束来处理这个特定的用例。但是,您可以创建自定义验证约束来实现所需的功能。这是你如何做到的:

  1. 创建自定义验证约束类: 创建一个新的约束类,该类将负责检查是否正在更新 joinedAt 属性。
// src/Validator/Constraints/NotUpdatable.php

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class NotUpdatable extends Constraint
{
    public $message = 'The "{{ field }}" field cannot be updated.';
}

  1. 创建自定义验证程序类: 接下来,创建一个将执行验证的自定义验证程序类。
// src/Validator/Constraints/NotUpdatableValidator.php

namespace App\Validator\Constraints;

use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class NotUpdatableValidator extends ConstraintValidator
{
    private $doctrine;

    public function __construct(ManagerRegistry $doctrine)
    {
        $this->doctrine = $doctrine;
    }

    public function validate($value, Constraint $constraint)
    {
        $entity = $this->context->getObject();

        // Check if the entity is managed by Doctrine (already in the database)
        if ($this->doctrine->getManager()->contains($entity)) {
            $originalEntity = $this->doctrine->getManager()->getUnitOfWork()->getOriginalEntityData($entity);

            // Compare the original joinedAt value with the new one
            if ($originalEntity['joinedAt'] !== $value) {
                $this->context->buildViolation($constraint->message)
                    ->setParameter('{{ field }}', $this->context->getPropertyName())
                    ->addViolation();
            }
        }
    }
}

  1. 将自定义约束应用于实体属性: 现在,将自定义 NotUpdatable 约束应用于实体类中的 joinedAt 属性:
// src/Entity/User.php

namespace App\Entity;

use App\Validator\Constraints as CustomAssert;
use Symfony\Component\Validator\Constraints as Assert;

class User
{
    // ...

    /**
     * @ORM\Column(updatable: false)
     * @Assert\Type(
     *     type='object',
     *     message='The value {{ value }} is not a valid {{ type }}.'
     * )
     * @CustomAssert\NotUpdatable
     */
    private ?\DateTimeImmutable $joinedAt = null;

    // ...
}
  1. 更新测试以触发验证: 最后,您可以更新 PHPUnit 测试以触发验证,并确保无法更新 joinedAt 属性:
public function testInvalidJoinedAt(): void
{
    $manager = static::getContainer()->get(EntityManagerInterface::class);

    // User joinedAt cannot be updated
    $now = new DateTimeImmutable();
    $existingUser = $this->getRepository(UserRepository::class)->find(0);
    $existingUser->setJoinedAt($now);

    $validator = static::getContainer()->get('validator');
    $violations = $validator->validate($existingUser);

    $this->assertCount(1, $violations);
    $this->assertEquals('The "joinedAt" field cannot be updated.', $violations[0]->getMessage());
}

此测试将确保无法更新 joinedAt 属性,并将验证已创建的自定义约束。如果违反了约束,它将捕获冲突,您可以断言错误消息,如上所示。

评论

0赞 Naico04 9/13/2023
我不得不稍微修改一下代码。我不知道为什么,但是->getManager()->contains($entity)和->getManager()->getUnitOfWork()->getOriginalEntityData($entity)无法正常工作。但是,逻辑很清楚,我已经设法适应了它。
0赞 kasali 9/13/2023
好的,你有什么错误?