Skip to content

Commit e2d058d

Browse files
committed
[Form] Made submit buttons able to convey validation groups
1 parent 3c1bc9d commit e2d058d

File tree

7 files changed

+206
-10
lines changed

7 files changed

+206
-10
lines changed

Extension/Core/Type/BaseType.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4040
*/
4141
public function buildView(FormView $view, FormInterface $form, array $options)
4242
{
43-
/* @var \Symfony\Component\Form\ClickableInterface $form */
44-
4543
$name = $form->getName();
4644
$blockName = $options['block_name'] ?: $form->getName();
4745
$translationDomain = $options['translation_domain'];

Extension/Validator/Constraints/FormValidator.php

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Form\Extension\Validator\Constraints;
1313

14+
use Symfony\Component\Form\ClickableInterface;
1415
use Symfony\Component\Form\FormInterface;
1516
use Symfony\Component\Form\Extension\Validator\Util\ServerParams;
1617
use Symfony\Component\Validator\Constraint;
@@ -171,20 +172,65 @@ private static function allowDataWalking(FormInterface $form)
171172
*/
172173
private static function getValidationGroups(FormInterface $form)
173174
{
175+
$button = self::findClickedButton($form->getRoot());
176+
177+
if (null !== $button) {
178+
$groups = $button->getConfig()->getOption('validation_groups');
179+
180+
if (null !== $groups) {
181+
return self::resolveValidationGroups($groups, $form);
182+
}
183+
}
184+
174185
do {
175186
$groups = $form->getConfig()->getOption('validation_groups');
176187

177188
if (null !== $groups) {
178-
if (is_callable($groups)) {
179-
$groups = call_user_func($groups, $form);
180-
}
181-
182-
return (array) $groups;
189+
return self::resolveValidationGroups($groups, $form);
183190
}
184191

185192
$form = $form->getParent();
186193
} while (null !== $form);
187194

188195
return array(Constraint::DEFAULT_GROUP);
189196
}
197+
198+
/**
199+
* Extracts a clicked button from a form tree, if one exists.
200+
*
201+
* @param FormInterface $form The root form.
202+
*
203+
* @return ClickableInterface|null The clicked button or null.
204+
*/
205+
private static function findClickedButton(FormInterface $form)
206+
{
207+
if ($form instanceof ClickableInterface && $form->isClicked()) {
208+
return $form;
209+
}
210+
211+
foreach ($form as $child) {
212+
if (null !== ($button = self::findClickedButton($child))) {
213+
return $button;
214+
}
215+
}
216+
217+
return null;
218+
}
219+
220+
/**
221+
* Post-processes the validation groups option for a given form.
222+
*
223+
* @param array|callable $groups The validation groups.
224+
* @param FormInterface $form The validated form.
225+
*
226+
* @return array The validation groups.
227+
*/
228+
private static function resolveValidationGroups($groups, FormInterface $form)
229+
{
230+
if (is_callable($groups)) {
231+
$groups = call_user_func($groups, $form);
232+
}
233+
234+
return (array) $groups;
235+
}
190236
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\Type;
13+
14+
use Symfony\Component\Form\AbstractTypeExtension;
15+
use Symfony\Component\OptionsResolver\Options;
16+
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
17+
18+
/**
19+
* Encapsulates common logic of {@link FormTypeValidatorExtension} and
20+
* {@link SubmitTypeValidatorExtension}.
21+
*
22+
* @author Bernhard Schussek <[email protected]>
23+
*/
24+
abstract class BaseValidatorExtension extends AbstractTypeExtension
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function setDefaultOptions(OptionsResolverInterface $resolver)
30+
{
31+
// Make sure that validation groups end up as null, closure or array
32+
$validationGroupsNormalizer = function (Options $options, $groups) {
33+
if (empty($groups)) {
34+
return null;
35+
}
36+
37+
if (is_callable($groups)) {
38+
return $groups;
39+
}
40+
41+
return (array) $groups;
42+
};
43+
44+
$resolver->setDefaults(array(
45+
'validation_groups' => null,
46+
));
47+
48+
$resolver->setNormalizers(array(
49+
'validation_groups' => $validationGroupsNormalizer,
50+
));
51+
}
52+
}

Extension/Validator/Type/FormTypeValidatorExtension.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/**
2323
* @author Bernhard Schussek <[email protected]>
2424
*/
25-
class FormTypeValidatorExtension extends AbstractTypeExtension
25+
class FormTypeValidatorExtension extends BaseValidatorExtension
2626
{
2727
/**
2828
* @var ValidatorInterface
@@ -53,6 +53,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
5353
*/
5454
public function setDefaultOptions(OptionsResolverInterface $resolver)
5555
{
56+
parent::setDefaultOptions($resolver);
57+
5658
// BC clause
5759
$constraints = function (Options $options) {
5860
return $options['validation_constraint'];
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\Type;
13+
14+
use Symfony\Component\Form\AbstractTypeExtension;
15+
16+
/**
17+
* @author Bernhard Schussek <[email protected]>
18+
*/
19+
class SubmitTypeValidatorExtension extends AbstractTypeExtension
20+
{
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function getExtendedType()
25+
{
26+
return 'submit';
27+
}
28+
}

Extension/Validator/ValidatorExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ protected function loadTypeExtensions()
5151
return array(
5252
new Type\FormTypeValidatorExtension($this->validator),
5353
new Type\RepeatedTypeValidatorExtension(),
54+
new Type\SubmitTypeValidatorExtension(),
5455
);
5556
}
5657
}

Tests/Extension/Validator/Constraints/FormValidatorTest.php

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Form\FormInterface;
1818
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
1919
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
20+
use Symfony\Component\Form\SubmitButtonBuilder;
2021
use Symfony\Component\Validator\Constraint;
2122
use Symfony\Component\Validator\Constraints\NotNull;
2223
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -313,6 +314,62 @@ public function testHandleClosureValidationGroups()
313314
$this->validator->validate($form, new Form());
314315
}
315316

317+
public function testUseValidationGroupOfClickedButton()
318+
{
319+
$context = $this->getMockExecutionContext();
320+
$object = $this->getMock('\stdClass');
321+
322+
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
323+
->setCompound(true)
324+
->setDataMapper($this->getDataMapper())
325+
->getForm();
326+
$form = $this->getForm('name', '\stdClass', array(
327+
'validation_groups' => 'form_group',
328+
));
329+
330+
$parent->add($form);
331+
$parent->add($this->getClickedSubmitButton('submit', array(
332+
'validation_groups' => 'button_group',
333+
)));
334+
335+
$form->setData($object);
336+
337+
$context->expects($this->once())
338+
->method('validate')
339+
->with($object, 'data', 'button_group', true);
340+
341+
$this->validator->initialize($context);
342+
$this->validator->validate($form, new Form());
343+
}
344+
345+
public function testDontUseValidationGroupOfUnclickedButton()
346+
{
347+
$context = $this->getMockExecutionContext();
348+
$object = $this->getMock('\stdClass');
349+
350+
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
351+
->setCompound(true)
352+
->setDataMapper($this->getDataMapper())
353+
->getForm();
354+
$form = $this->getForm('name', '\stdClass', array(
355+
'validation_groups' => 'form_group',
356+
));
357+
358+
$parent->add($form);
359+
$parent->add($this->getSubmitButton('submit', array(
360+
'validation_groups' => 'button_group',
361+
)));
362+
363+
$form->setData($object);
364+
365+
$context->expects($this->once())
366+
->method('validate')
367+
->with($object, 'data', 'form_group', true);
368+
369+
$this->validator->initialize($context);
370+
$this->validator->validate($form, new Form());
371+
}
372+
316373
public function testUseInheritedValidationGroup()
317374
{
318375
$context = $this->getMockExecutionContext();
@@ -561,9 +618,21 @@ private function getBuilder($name = 'name', $dataClass = null, array $options =
561618
return new FormBuilder($name, $dataClass, $this->dispatcher, $this->factory, $options);
562619
}
563620

564-
private function getForm($name = 'name', $dataClass = null)
621+
private function getForm($name = 'name', $dataClass = null, array $options = array())
622+
{
623+
return $this->getBuilder($name, $dataClass, $options)->getForm();
624+
}
625+
626+
private function getSubmitButton($name = 'name', array $options = array())
627+
{
628+
$builder = new SubmitButtonBuilder($name, $options);
629+
630+
return $builder->getForm();
631+
}
632+
633+
private function getClickedSubmitButton($name = 'name', array $options = array())
565634
{
566-
return $this->getBuilder($name, $dataClass)->getForm();
635+
return $this->getSubmitButton($name, $options)->bind('');
567636
}
568637

569638
/**

0 commit comments

Comments
 (0)