Symfony2 表单验证器 - 在刷新之前比较旧值和新值

Posted

技术标签:

【中文标题】Symfony2 表单验证器 - 在刷新之前比较旧值和新值【英文标题】:Symfony2 Form Validator - Comparing old and new values before flush 【发布时间】:2013-06-22 19:10:46 【问题描述】:

我想知道是否有办法在刷新之前比较实体内验证器中的旧值和新值。

我有一个Server 实体,它可以很好地呈现为一个表单。该实体与status (N->1) 有关系,当状态从Unracked 更改为Racked 时,需要检查对服务器的SSH 和FTP 访问。如果无法访问,验证器应该会失败。

我已将验证器回调映射到Server 实体中的方法isServerValid(),如此处所述 http://symfony.com/doc/current/reference/constraints/Callback.html。我显然可以通过$this->status 访问“新”值,但我怎样才能获得原始值?

在伪代码中,如下所示:

public function isAuthorValid(ExecutionContextInterface $context)

    $original = ... ; // get old values
    if( $this->status !== $original->status && $this->status === 'Racked' && $original->status === 'Unracked' )
    
        // check ftp and ssh connection
        // $context->addViolationAt('status', 'Unable to connect etc etc');
    

提前致谢!

【问题讨论】:

【参考方案1】:

为了记录,这里是使用 Symfony5 的方法。

首先,您需要在验证器的构造函数中注入 EntityManagerInterface 服务。 然后,使用它来检索原始实体。

/** @var EntityManagerInterface */
private $entityManager;

/**
 * MyValidator constructor.
 * @param EntityManagerInterface $entityManager
 */
public function __construct(EntityManagerInterface $entityManager)

    $this->entityManager = $entityManager;


/**
 * @param string $value
 * @param Constraint $constraint
 */
public function validate($value, Constraint $constraint)
    
    $originalEntity = $this->entityManager
        ->getUnitOfWork()
        ->getOriginalEntityData($this->context->getObject());

    // ...

【讨论】:

【参考方案2】:

以前的答案完全有效,可能适合您的用例。

对于“简单”的用例,它可能会很重。 在实体可通过(仅)表单编辑的情况下,您可以简单地在 FormBuilder 上添加约束:

<?php

namespace AppBundle\Form\Type;

// ...

use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;

/**
 * Class MyFormType
 */
class MyFormType extends AbstractType

    /**
     * @inheritdoc
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('fooField', IntegerType::class, [
                'constraints' => [
                    new GreaterThanOrEqual(['value' => $builder->getData()->getFooField()])
                ]
            ])
        ;
    

这适用于任何 Symfony 2+ 版本。

【讨论】:

【参考方案3】:

Symfony 2.5 的完整示例 (http://symfony.com/doc/current/cookbook/validation/custom_constraint.html)

在此示例中,实体“NoDecreasingInteger”的字段“integerField”的新值必须高于存储的值。

创建约束:

// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnly.php;
<?php
namespace Acme\AcmeBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class IncrementOnly extends Constraint

  public $message = 'The new value %new% is least than the old %old%';

  public function getTargets()
  
    return self::CLASS_CONSTRAINT;
  

  public function validatedBy()
  
    return 'increment_only';
  

创建约束验证器:

// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnlyValidator.php
<?php
namespace Acme\AcmeBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

use Doctrine\ORM\EntityManager;

class IncrementOnlyValidator extends ConstraintValidator

  protected $em;

  public function __construct(EntityManager $em)
  
    $this->em = $em;
  

  public function validate($object, Constraint $constraint)
  
    $new_value = $object->getIntegerField();

    $old_data = $this->em
      ->getUnitOfWork()
      ->getOriginalEntityData($object);

    // $old_data is empty if we create a new NoDecreasingInteger object.
    if (is_array($old_data) and !empty($old_data))
      
        $old_value = $old_data['integerField'];

        if ($new_value < $old_value)
          
            $this->context->buildViolation($constraint->message)
              ->setParameter("%new%", $new_value)
              ->setParameter('%old%', $old_value)
              ->addViolation();
          
      
  

将验证器绑定到实体:

// src/Acme/AcmeBundle/Resources/config/validator.yml
Acme\AcmeBundle\Entity\NoDecreasingInteger:
  constraints:
     - Acme\AcmeBundle\Validator\Constraints\IncrementOnly: ~

将 EntityManager 注入到 IncrementOnlyValidator:

// src/Acme/AcmeBundle/Resources/config/services.yml
services:
   validator.increment_only:
        class: Acme\AcmeBundle\Validator\Constraints\IncrementOnlyValidator
        arguments: ["@doctrine.orm.entity_manager"]
        tags:
            -  name: validator.constraint_validator, alias: increment_only 

【讨论】:

这个getOriginalEntityData()方法真的很方便! 非常好的解释。与 SF3.1 完美配合 好主意,但它不适用于集合,因为$objects 属性中的集合与$old_data 中的集合相同(相同的引用)。因此,如果元素在表单中被删除,您将不会获得旧值。【参考方案4】:

在 symfony2 的自定义验证器中访问 EntityManager

您可以检查控制器操作中的先前值...但这并不是一个真正的解决方案!

普通的表单验证只会访问绑定到表单的数据......默认情况下没有“以前的”数据可访问。

您尝试使用的回调约束无权访问容器或任何其他服务......因此您无法轻松访问实体管理器(或任何先前的数据提供者)来检查先前的值。

您需要的是custom validator 上的class level。需要类级别,因为如果要获取实体,您不仅需要访问整个对象,还需要访问单个值。

验证器本身可能如下所示:

namespace Vendor\YourBundle\Validation\Constraints;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class StatusValidator extends ConstraintValidator

    protected $container;

    public function __construct(ContainerInterface $container)
    
        $this->container = $container;
    

    public function validate($status, Constraint $constraint)
    

        $em = $this->container->get('doctrine')->getEntityManager('default');

        $previousStatus = $em->getRepository('YourBundle:Status')->findOneBy(array('id' => $status->getId()));

        // ... do something with the previous status here

        if ( $previousStatus->getValue() != $status->getValue() ) 
            $this->context->addViolationAt('whatever', $constraint->message, array(), null);
        
    

    public function getTargets()
    
        return self::CLASS_CONSTRAINT;
    

    public function validatedBy()
    
       return 'previous_value';
    

...之后将验证器注册为服务并将其标记为验证器

services:
    validator.previous_value:
        class: Vendor\YourBundle\Validation\Constraints\StatusValidator

        # example! better inject only the services you need ... 
        # i.e. ... @doctrine.orm.entity_manager

        arguments: [ @service_container ]         
        tags:
            -  name: validator.constraint_validator, alias: previous_value 

最后为您的状态实体使用约束(即使用注释)

use Vendor\YourBundle\Validation\Constraints as MyValidation;

/**
 * @MyValidation\StatusValidator
 */
class Status 

【讨论】:

快速提示:请注意,由于内部对象缓存,Doctrine 可能会为您提供与 $previousState 完全相同的对象。有我的问题。 :[ 在这种情况下,您需要在工作/绑定/等之前将前一个对象克隆到一个新对象中(使用 PHP 的“clone()”函数)。新的。 我在使用 Doctrine 时遇到问题......即使我在验证器中使用 $query-&gt;useResultCache(false);,它也给了我相同的对象/值。在表单/控制器中操作对象之前克隆对象最终会复制数据库条目......那么做这一切的正确方法是什么? @nifr 您应该注入所需的服务而不是容器。 :) @CSchulz 我完全意识到通过注入直接依赖项而不是容器来改进性能和可测试性。这个答案已有 1.5 年历史,仅作为一个简单示例 - 随意编辑/改进,这就是 *** 的意义所在。

以上是关于Symfony2 表单验证器 - 在刷新之前比较旧值和新值的主要内容,如果未能解决你的问题,请参考以下文章

Symfony2 Doctrine ODM 嵌入类表单验证

Symfony2 基于两个字段的表单验证

symfony2 表单验证单选按钮

Symfony2 Form + AngularJS for REST API = 表单验证额外字段错误

未处理模态中的 Symfony2 表单

将旧用户迁移到 symfony2