Created
November 29, 2019 11:18
-
-
Save webdevilopers/541fd5dab743cde894b88212b4720649 to your computer and use it in GitHub Desktop.
Dynamic form validation groups with callback in Symfony 2.8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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'; | |
} | |
} |
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
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