Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Dynamic form validation groups with callback in Symfony 2.8
<?php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class MaterialQuantityDto
{
/**
* @var string
* @Assert\NotNull(groups={"available_material"})
* @Assert\Type(type="string")
*/
public $materialId;
/**
* @var bool
* @Assert\NotNull(groups={"available_material"})
* @Assert\Type(type="bool")
*/
public $isAvailable;
/**
* @var string[]
*/
public $claddingMaterials;
/**
* @var int
* @Assert\NotNull(groups={"available_material"})
* @Assert\NotBlank(groups={"availableMaterial"})
* @Assert\Type(type="float", groups={"available_material"})
* @Assert\Range(min="0", groups={"available_material"})
*/
public $quantity;
/** @Assert\Callback(groups={"availableMaterial"}) */
public function checkAvailability(ExecutionContextInterface $context)
{
dump($context->getGroup());
}
}
<?php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class MaterialQuantityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'isAvailable', CheckboxType::class, [
'label' => false,
'required' => false
])
->add(
'quantity', NumberType::class, [
'required' => true,
'attr' => ['min' => 1]
])
->add(
'claddingMaterials', WallCladdingMaterialType::class, [
'required' => true,
'choice_translation_domain' => 'cladding_material',
'multiple' => true
]);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($options) {
$data = $event->getData();
$form = $event->getForm();
// Skip empty data when collection is initialized
if (null === $options['materialData']) {
return;
}
$this->addMaterialId($form, $options);
}
);
}
private function addMaterialId($form, array $options)
{
$form
->add('materialId', HiddenType::class, [
'required' => true,
'label' => $options['materialData']['name'],
'data' => $options['materialData']['id']
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MaterialQuantityDto::class,
'materialData' => null,
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if (false === $data->isAvailable) {
return ['unavailable_material'];
}
return ['availableMaterial'];
},
'cascade_validation' => true,
'translation_domain' => 'dormer_construction_element'
]);
}
public function getBlockPrefix()
{
return 'dormer_construction_element_material_quantity';
}
}
@webdevilopers

This comment has been minimized.

Copy link
Owner Author

@webdevilopers webdevilopers commented Nov 29, 2019

Came from:
https://twitter.com/webdevilopers/status/1200375985100730370

When dumping the $resolver it shows "validation_groups" => "Object(Closure)".
Dumping the $context->getGroup() always shows Default.

Maybe this is a problem with the closure in Symfony 2.8? Docs:
https://symfony.com/doc/2.8/form/data_based_validation.html

@webdevilopers

This comment has been minimized.

Copy link
Owner Author

@webdevilopers webdevilopers commented Nov 29, 2019

Here is my current workaround which I prefer since it does not require form manipulation at all - this command DTO would for instance also work with the Symfony Messenger and Validation middleware:

    /** @Assert\Callback() */
    public function checkAvailability(ExecutionContextInterface $context)
    {
        if (false !== $this->isAvailable) {
            $context->getValidator()
                ->inContext($context)
                ->atPath('claddingMaterials')
                ->validate(
                    $this->claddingMaterials,
                    new Assert\Count(["min" => 1])
                );

            $context->getValidator()
                ->inContext($context)
                ->atPath('quantity')
                ->validate(
                    $this->quantity,
                    new Assert\NotNull()
                );
        }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment