From 7b438a816b5716ec4bf1fb0097b26243d1ad6bfa Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Jan 2013 18:51:49 +0100 Subject: [PATCH] [Form] Made submit buttons able to convey validation groups --- .../FrameworkBundle/Resources/config/form.xml | 3 + .../Form/Extension/Core/Type/BaseType.php | 2 - .../Validator/Constraints/FormValidator.php | 56 ++++++++++++-- .../Validator/Type/BaseValidatorExtension.php | 52 +++++++++++++ .../Type/FormTypeValidatorExtension.php | 4 +- .../Type/SubmitTypeValidatorExtension.php | 28 +++++++ .../Validator/ValidatorExtension.php | 1 + .../Constraints/FormValidatorTest.php | 73 ++++++++++++++++++- 8 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php create mode 100644 src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index a70e06becb..17e459b9ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -163,5 +163,8 @@ + + + diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php index ca47a7bc2f..79333a6799 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -40,8 +40,6 @@ abstract class BaseType extends AbstractType */ public function buildView(FormView $view, FormInterface $form, array $options) { - /* @var \Symfony\Component\Form\ClickableInterface $form */ - $name = $form->getName(); $blockName = $options['block_name'] ?: $form->getName(); $translationDomain = $options['translation_domain']; diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 167fcd95e9..610329c030 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Extension\Validator\Constraints; +use Symfony\Component\Form\ClickableInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Validator\Util\ServerParams; use Symfony\Component\Validator\Constraint; @@ -171,15 +172,21 @@ class FormValidator extends ConstraintValidator */ private static function getValidationGroups(FormInterface $form) { + $button = self::findClickedButton($form->getRoot()); + + if (null !== $button) { + $groups = $button->getConfig()->getOption('validation_groups'); + + if (null !== $groups) { + return self::resolveValidationGroups($groups, $form); + } + } + do { $groups = $form->getConfig()->getOption('validation_groups'); if (null !== $groups) { - if (is_callable($groups)) { - $groups = call_user_func($groups, $form); - } - - return (array) $groups; + return self::resolveValidationGroups($groups, $form); } $form = $form->getParent(); @@ -187,4 +194,43 @@ class FormValidator extends ConstraintValidator return array(Constraint::DEFAULT_GROUP); } + + /** + * Extracts a clicked button from a form tree, if one exists. + * + * @param FormInterface $form The root form. + * + * @return ClickableInterface|null The clicked button or null. + */ + private static function findClickedButton(FormInterface $form) + { + if ($form instanceof ClickableInterface && $form->isClicked()) { + return $form; + } + + foreach ($form as $child) { + if (null !== ($button = self::findClickedButton($child))) { + return $button; + } + } + + return null; + } + + /** + * Post-processes the validation groups option for a given form. + * + * @param array|callable $groups The validation groups. + * @param FormInterface $form The validated form. + * + * @return array The validation groups. + */ + private static function resolveValidationGroups($groups, FormInterface $form) + { + if (is_callable($groups)) { + $groups = call_user_func($groups, $form); + } + + return (array) $groups; + } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php new file mode 100644 index 0000000000..cf5713058d --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +/** + * Encapsulates common logic of {@link FormTypeValidatorExtension} and + * {@link SubmitTypeValidatorExtension}. + * + * @author Bernhard Schussek + */ +abstract class BaseValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + // Make sure that validation groups end up as null, closure or array + $validationGroupsNormalizer = function (Options $options, $groups) { + if (empty($groups)) { + return null; + } + + if (is_callable($groups)) { + return $groups; + } + + return (array) $groups; + }; + + $resolver->setDefaults(array( + 'validation_groups' => null, + )); + + $resolver->setNormalizers(array( + 'validation_groups' => $validationGroupsNormalizer, + )); + } +} diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 19c888e200..ce106ecacc 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -22,7 +22,7 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * @author Bernhard Schussek */ -class FormTypeValidatorExtension extends AbstractTypeExtension +class FormTypeValidatorExtension extends BaseValidatorExtension { /** * @var ValidatorInterface @@ -53,6 +53,8 @@ class FormTypeValidatorExtension extends AbstractTypeExtension */ public function setDefaultOptions(OptionsResolverInterface $resolver) { + parent::setDefaultOptions($resolver); + // BC clause $constraints = function (Options $options) { return $options['validation_constraint']; diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php new file mode 100644 index 0000000000..5aad67fb3a --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; + +/** + * @author Bernhard Schussek + */ +class SubmitTypeValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return 'submit'; + } +} diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php index e6d6fea415..9cff22a276 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -51,6 +51,7 @@ class ValidatorExtension extends AbstractExtension return array( new Type\FormTypeValidatorExtension($this->validator), new Type\RepeatedTypeValidatorExtension(), + new Type\SubmitTypeValidatorExtension(), ); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index a3d9f26d98..14accc9a3d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Validator\Constraints\Form; use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator; +use Symfony\Component\Form\SubmitButtonBuilder; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\NotBlank; @@ -313,6 +314,62 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase $this->validator->validate($form, new Form()); } + public function testUseValidationGroupOfClickedButton() + { + $context = $this->getMockExecutionContext(); + $object = $this->getMock('\stdClass'); + + $parent = $this->getBuilder('parent', null, array('cascade_validation' => true)) + ->setCompound(true) + ->setDataMapper($this->getDataMapper()) + ->getForm(); + $form = $this->getForm('name', '\stdClass', array( + 'validation_groups' => 'form_group', + )); + + $parent->add($form); + $parent->add($this->getClickedSubmitButton('submit', array( + 'validation_groups' => 'button_group', + ))); + + $form->setData($object); + + $context->expects($this->once()) + ->method('validate') + ->with($object, 'data', 'button_group', true); + + $this->validator->initialize($context); + $this->validator->validate($form, new Form()); + } + + public function testDontUseValidationGroupOfUnclickedButton() + { + $context = $this->getMockExecutionContext(); + $object = $this->getMock('\stdClass'); + + $parent = $this->getBuilder('parent', null, array('cascade_validation' => true)) + ->setCompound(true) + ->setDataMapper($this->getDataMapper()) + ->getForm(); + $form = $this->getForm('name', '\stdClass', array( + 'validation_groups' => 'form_group', + )); + + $parent->add($form); + $parent->add($this->getSubmitButton('submit', array( + 'validation_groups' => 'button_group', + ))); + + $form->setData($object); + + $context->expects($this->once()) + ->method('validate') + ->with($object, 'data', 'form_group', true); + + $this->validator->initialize($context); + $this->validator->validate($form, new Form()); + } + public function testUseInheritedValidationGroup() { $context = $this->getMockExecutionContext(); @@ -561,9 +618,21 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase return new FormBuilder($name, $dataClass, $this->dispatcher, $this->factory, $options); } - private function getForm($name = 'name', $dataClass = null) + private function getForm($name = 'name', $dataClass = null, array $options = array()) { - return $this->getBuilder($name, $dataClass)->getForm(); + return $this->getBuilder($name, $dataClass, $options)->getForm(); + } + + private function getSubmitButton($name = 'name', array $options = array()) + { + $builder = new SubmitButtonBuilder($name, $options); + + return $builder->getForm(); + } + + private function getClickedSubmitButton($name = 'name', array $options = array()) + { + return $this->getSubmitButton($name, $options)->bind(''); } /**