数据转换器与约束

Posted

技术标签:

【中文标题】数据转换器与约束【英文标题】:Data Transformer vs.Constraints 【发布时间】:2020-07-02 11:59:24 【问题描述】:

我偶然发现了一个关于Symfony's DataTransformers 以及如何正确使用它们的问题。虽然我知道如何实现并将它们添加到我的表单字段中,但我想知道 DataTransformers 应该如何与 Constraints 结合使用。

以下代码显示了我的用例。

形式

<?php

namespace AppBundle\Form;

use AppBundle\Form\DataTransformer\Consent\ConsentTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\IsTrue;

class ConsentTestForm extends AbstractType

    /** @var ConsentTransformer $consentTransformer */
    private $consentTransformer;

    /**
     * ConsentTestForm constructor.
     * @param ConsentTransformer $consentTransformer
     */
    public function __construct(ConsentTransformer $consentTransformer)
    
        $this->consentTransformer = $consentTransformer;
    

    /**
     * @inheritDoc
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder->add('accountConsent', CheckboxType::class, [
            'constraints' => [
                new IsTrue()
            ]
        ]);
        $builder->get('accountConsent')->addModelTransformer($this->consentTransformer);

        $builder->add('submit', SubmitType::class);
    

模型

<?php

class User extends Concrete implements \Pimcore\Model\DataObject\DirtyIndicatorInterface

    protected $accountConsent;

    /**
     * ...
     */
    public function getAccountConsent () 
        // ...
    

    /**
     * ...
     */
    public function setAccountConsent ($accountConsent) 
        // ...
    

为简洁起见,省略了很多代码。型号是Pimcore class。

数据转换器

<?php

namespace Passioneight\Bundle\FormBuilderBundle\Form\DataTransformer\Consent;

use Pimcore\Model\DataObject\Data\Consent;
use Symfony\Component\Form\DataTransformerInterface;

class ConsentTransformer implements DataTransformerInterface

    /**
     * @inheritDoc
     * @param Consent|null $consent
     */
    public function transform($consent)
    
        return $consent instanceof Consent && $consent->getConsent();
    

    /**
     * @inheritDoc
     * @param bool|null $consented
     */
    public function reverseTransform($consented)
    
        $consent = new Consent();
        $consent->setConsent($consented ?: false);
        return $consent;
    


如您所见,任何提交的值(即nulltruefalse)都将转换为Consent,反之亦然。

控制器

<?php

namespace AppBundle\Controller;

use AppBundle\Form\ConsentTestForm;
use AppBundle\Model\DataObject\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Class TestController
 * @package AppBundle\Controller
 *
 * @Route("/test")
 */
class TestController extends AbstractFrontendController

    /**
     * @Route("/form")
     * @param Request $request
     * @return Response
     */
    public function formAction(Request $request)
    
        $user = new User();

        $form = $this->createForm(ConsentTestForm::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted()) 
            if ($form->isValid()) 
                p_r("VALID");
                p_r($user);
             else 
                p_r("NOT VALID");
            
        ;

        return $this->renderTemplate(':Test:form.html.twig', [
            "form" => $form->createView()
        ]);
    


请注意 new User() 如何作为实体传递,以便使用提交的值自动填充它。

观点

 form(form) 

问题

表单可以构建得很好,最终显示一个带有我指定标签的复选框。由于转换器,checked 状态甚至可以正确显示,因为transform 方法将Users Consent 转换为boolean

但是,在提交表单时,会显示一个错误,说需要帐户同意。虽然在未给予同意的情况下提交表单时这很好,但在真正同意时并不是理想的结果。

同意时,提交的值将转换为Consent,然后将保留值true。但是由于转换是在验证提交的值之前完成的,因此会显示前面提到的错误。出现这种情况是因为在表单中添加的accountConsent 字段有一个Constraint 集,即IsTrue。因此,IsTrueValidator 会验证 Consent(而不是实际提交的值)。

显然,IsTrueValidator 无法知道 Pimcore 的 Consent 类。

问题

所有这些都给我留下了一个问题:如何正确地将IsTrue-constraint 与我的ConsentDataTransformer 结合起来?

【问题讨论】:

在问题中提供过少信息和过多信息之间找到适当的平衡可能是一项挑战。我承认我迷失了你的细节。我建议尝试通过仅使用约束字段创建实体/表单来隔离问题。我认为view transformer 可能会有所帮助。但这真的只是一个猜测。 感谢您的反馈 - 非常感谢。我将创建一个更简单的用例,然后重新表述我的问题。不过,我可能需要一段时间才能找到时间这样做。我还会检查视图转换器是否有帮助,但视图转换器不会导致完全相同的问题,因为在转换之后进行了验证? 在这个网站上有点不受欢迎,但是创建一个新项目,添加重新创建问题所需的最低限度,然后将整个事情检查到 github 可以更容易解决这样的问题。只是一个想法。 我试图尽可能地简化问题 - 希望现在更清楚我想要实现的目标。我还需要一些时间来尝试addViewTransformer 方法而不是addModelTransformer 方法。 使用addViewTransformer 会导致类似的问题,因为CheckboxType 在底层添加了另一个视图转换器,即BooleanToStringTransformer。这个转换器期望传递的值是string 类型。由于提交的值已经转换为Consent,所以会抛出TransformationFailedException,导致表单无效。 【参考方案1】:

验证的问题是您试图将对象验证为布尔类型。当您尝试验证并在提交表单时转换时,总是会执行约束。所以您已经转换了数据,这就是为什么IsBool 验证失败的原因,因为该值属于 Consent 对象类型;不是布尔值。

要解决这个问题,您必须创建新的验证约束来覆盖 IsTrue。

<?php

namespace App\Form\Validator;

use Symfony\Component\Validator\Constraints\IsTrue;

class IsConsented extends IsTrue

    public $message = 'You need to consent!';

和一个验证器在同一个命名空间上;

<?php

namespace App\Form\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\IsTrueValidator;

class IsConsentedValidator extends IsTrueValidator

    public function validate($value, Constraint $constraint)
    
        return parent::validate($value->getConsent(), $constraint);
    

然后你需要用IsConsented改变你的IsTrue约束,如下所示;

<?php

namespace App\Form;

use App\Entity\User;
use App\Form\DataTransformer\ConsentTransformer;
use App\Form\Validator\IsConsented;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ConsentTestFormType extends AbstractType

    /** @var ConsentTransformer $consentTransformer */
    private $consentTransformer;

    /**
     * ConsentTestForm constructor.
     * @param ConsentTransformer $consentTransformer
     */
    public function __construct(ConsentTransformer $consentTransformer)
    
        $this->consentTransformer = $consentTransformer;
    

    /**
     * @inheritDoc
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('accountConsent', CheckboxType::class, [
            'constraints' => [
                new IsConsented()
            ]
        ]);
        $builder->get('accountConsent')->addModelTransformer($this->consentTransformer);

        $builder->add('submit', SubmitType::class);
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    


就是这样。您的表格现在有效。输出应该是这样的;

FormController.php on line 30:
"VALID"

【讨论】:

感谢您提出此解决方案。不幸的是,我已经意识到这一点,我正在寻找一种不同的方法来解决我的问题。原因是我自己创建了一个包并且不想让开发人员知道自定义约束(因为我相信它只会增加复杂性)。然而,在这一点上,它似乎是唯一的解决方案,所以我会接受这个答案,因为它确实解决了问题并且有适当的记录。

以上是关于数据转换器与约束的主要内容,如果未能解决你的问题,请参考以下文章

转换,约束,魔术常量

DBMS-数据库设计与E-R模型:E-R模型约束E-R图E-R扩展特性E-R图转换为关系模式UML建模

Kerberos 约束委派与 Linux 上 Java 中的协议转换

Js特点,数据类型及转换

ASP.NET 4 中的双跳模拟、协议转换和约束委派

数据结构与算法之深入解析“把二叉搜索树转换为累加树”和“从二叉搜索树到更大和树”的求解思路与算法示例