Description
Hi,
For background, I am attempting to write a FormTypeExtension
that serialises form field constraints into data attributes on the input elements so that it is possible to write a generalised javascript library capable of validating most typical symfony forms client side.
One place that breaks down is when validation rules depend on submitted data - I've seen and have been using this to successfully validate on the server side, but without the ability to define field interdependencies declaratively, it becomes impossible to bake these rules into data attributes: there is no way to serialise the decision that happens in that closure.
With that in mind, I've been attempting to build another FormTypeExtension
that introduces the notion of dependencies, see comments inline:
<?php
namespace App\Form\Extension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use App\Form\EventListener\DependsListener;
class DependsExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return 'form';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setOptional(array(
'depends'
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (array_key_exists('depends', $options)) {
// should builder be needed here? is this even correct?
$builder->addEventSubscriber(new DependsListener($builder, $options['depends']));
}
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
// bake dependencies into data attributes for client side js to act on
if (array_key_exists('depends', $options)) {
$view->vars['attr']['data-depends'] = json_encode($options['depends']);
}
}
}
And the listener:
<?php
namespace App\Form\EventListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\PropertyAccess\PropertyAccess;
class DependsListener implements EventSubscriberInterface
{
/**
*
* @var FormBuilderInterface
*/
private $builder;
private $depends;
public function __construct($builder, $depends)
{
$this->builder = $builder;
$this->depends = $depends;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
)
62BB
;
}
public function preSetData(FormEvent $event)
{
// TODO probably have to do something here when loading the form
}
public function preSubmit(FormEvent $event)
{
$root = $event->getForm()->getRoot();
$accessor = PropertyAccess::createPropertyAccessor();
$removeMe = true;
foreach ($this->depends as $fieldPath => $value) {
// TODO: use sf constraints for this? allows for more options than simple equality
if ($accessor->getValue($root, $fieldPath)->getData() != $value) {
$removeMe = $removeMe && true;
}
else {
$removeMe = false;
}
}
if ($removeMe) {
// this is where I'd like to somehow unset / interact with the constraints defined on the field
//
// note it is possible to just straight up remove the field:
$event->getForm()->getParent()->remove($event->getForm()->getName());
// but this is not ideal. ideally we'd disable server side validation and hide this field with js
}
}
}
As you can see, it seems impossible to unset / interact with the constraints associated with the field, even though it's possible to simply outright delete the field.
Could anyone shed any light on if what I'm trying to do is possible / desired / insane / stupid and help point me in the direction of a solution?
Thanks,
Luke Cawood