数据转换器 vs.Constraints
Data Transformer vs.Constraints
我偶然发现了一个关于 Symfony
's DataTransformer
s and how to properly use them. While I know how to implement and add them to my form field, I was wondering how DataTransformer
s are supposed to be combined with Constraint
s 的问题。
The following code shows my use case.
形式
<?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) {
// ...
}
}
A lot of code was omitted for the sake of brevity. The model is a Pimcore class.
DataTransformer
<?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;
}
}
As you can see any submitted value (i.e., null
, true
, false
) will be converted to a Consent
and vice-versa.
控制器
<?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()
]);
}
}
Note how a new User()
is passed as entity in order to automatically populate it with the submitted values.
景观
{{ form(form) }}
问题
可以很好地构建表单,最终显示带有我指定标签的复选框。由于转换器,checked
状态甚至可以正确显示,因为 transform
方法将 User
s Consent
转换为 boolean
.
但是提交表单时,提示需要account-consent。虽然在未经同意的情况下提交表单这很好,但在真正同意时这并不是理想的结果。
同意后,提交的值将转换为 Consent
,然后将保留值 true
。但是由于 转换是在验证提交的值之前完成的 ,所以会显示上述错误。发生这种情况是因为在表单中添加的 accountConsent
字段有一个 Constraint
集,即 IsTrue
。因此,IsTrueValidator
验证了 Consent
(而不是实际提交的值)。
Obviously, the IsTrueValidator
cannot know about Pimcore's Consent
class.
问题
所有这些给我留下了一个问题:我如何正确地将 IsTrue
-约束与我的 ConsentDataTransformer
结合起来?
验证问题是您试图将对象验证为布尔类型。当您在提交表单时尝试验证和转换时,约束总是被执行。所以你已经转换了数据,这就是为什么 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"
我偶然发现了一个关于 Symfony
's DataTransformer
s and how to properly use them. While I know how to implement and add them to my form field, I was wondering how DataTransformer
s are supposed to be combined with Constraint
s 的问题。
The following code shows my use case.
形式
<?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) {
// ...
}
}
A lot of code was omitted for the sake of brevity. The model is a Pimcore class.
DataTransformer
<?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;
}
}
As you can see any submitted value (i.e.,
null
,true
,false
) will be converted to aConsent
and vice-versa.
控制器
<?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()
]);
}
}
Note how a
new User()
is passed as entity in order to automatically populate it with the submitted values.
景观
{{ form(form) }}
问题
可以很好地构建表单,最终显示带有我指定标签的复选框。由于转换器,checked
状态甚至可以正确显示,因为 transform
方法将 User
s Consent
转换为 boolean
.
但是提交表单时,提示需要account-consent。虽然在未经同意的情况下提交表单这很好,但在真正同意时这并不是理想的结果。
同意后,提交的值将转换为 Consent
,然后将保留值 true
。但是由于 转换是在验证提交的值之前完成的 ,所以会显示上述错误。发生这种情况是因为在表单中添加的 accountConsent
字段有一个 Constraint
集,即 IsTrue
。因此,IsTrueValidator
验证了 Consent
(而不是实际提交的值)。
Obviously, the
IsTrueValidator
cannot know about Pimcore'sConsent
class.
问题
所有这些给我留下了一个问题:我如何正确地将 IsTrue
-约束与我的 ConsentDataTransformer
结合起来?
验证问题是您试图将对象验证为布尔类型。当您在提交表单时尝试验证和转换时,约束总是被执行。所以你已经转换了数据,这就是为什么 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"