I stumbled upon a question regarding 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.
The Form
<?phpnamespace 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); }}
The Model
<?phpclass 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.
The DataTransformer
<?phpnamespace 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.
The Controller
<?phpnamespace 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.
The View
{{ form(form) }}
The Problem
The form can be built just fine, ultimately, displaying a checkbox with my specified label. Due to the transformer, the checked
-state is even correctly displayed, as the transform
method converts the User
s Consent
into a boolean
.
However, when submitting the form, an error is displayed, saying that the account-consent is required. While this is fine when it comes to submitting the form without giving consent, it's not quite the desired outcome when acutally consenting.
When consenting, the submitted value is converted to a Consent
, which will then hold the value true
. But since the transformation is done before the submitted value is validated the beforementioned error is displayed. This happens, because the accountConsent
field that was added in the form has a Constraint
set, namely, IsTrue
. Due to this, the IsTrueValidator
validates the Consent
(instead of the actually submitted value).
Obviously, the
IsTrueValidator
cannot know about Pimcore'sConsent
class.
The Question
All of this leaves me with the question: how do I properly combine the IsTrue
-constraint with my ConsentDataTransformer
?