8000 Merge branch '5.0' · symfony/symfony@69b6c90 · GitHub
[go: up one dir, main page]

Skip to content

Commit 69b6c90

Browse files
Merge branch '5.0'
* 5.0: [HttpFoundation] workaround PHP bug in the session module [SecurityBundle] fix accepting env vars in remember-me configurations [Form] Fixed handling groups sequence validation [Mime] Ensure proper line-ending for SMIME [Cache] Avoid memory leak in TraceableAdapter::reset()
2 parents 23f5070 + efbe752 commit 69b6c90

File tree

10 files changed

+237
-38
lines changed

10 files changed

+237
-38
lines changed

src/Symfony/Component/Cache/Adapter/TraceableAdapter.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,11 @@ public function prune()
244244
*/
245245
public function reset()
246246
{
247-
if (!$this->pool instanceof ResetInterface) {
248-
return;
249-
}
250-
$event = $this->start(__FUNCTION__);
251-
try {
247+
if ($this->pool instanceof ResetInterface) {
252248
$this->pool->reset();
253-
} finally {
254-
$event->end = microtime(true);
255249
}
250+
251+
$this->clearCalls();
256252
}
257253

258254
/**

src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php

Lines changed: 59 additions & 20 deletions
211
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525
class FormValidator extends ConstraintValidator
2626
{
27+
private $resolvedGroups;
28+
2729
/**
2830
* {@inheritdoc}
2931
*/
@@ -44,44 +46,70 @@ public function validate($form, Constraint $formConstraint)
4446

4547
if ($form->isSubmitted() && $form->isSynchronized()) {
4648
// Validate the form data only if transformation succeeded
47-
$groups = self::getValidationGroups($form);
49+
$groups = $this->getValidationGroups($form);
4850

4951
if (!$groups) {
5052
return;
5153
}
5254

5355
$data = $form->getData();
54-
5556
// Validate the data against its own constraints
56-
if ($form->isRoot() && (\is_object($data) || \is_array($data))) {
57-
if (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) {
58-
$validator->atPath('data')->validate($form->getData(), null, $groups);
59-
}
60-
}
57+
$validateDataGraph = $form->isRoot()
58+
&& (\is_object($data) || \is_array($data))
59+
&& (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups))
60+
;
6161

62-
// Validate the data against the constraints defined
63-
// in the form
62+
// Validate the data against the constraints defined in the form
63+
/** @var Constraint[] $constraints */
6464
$constraints = $config->getOption('constraints', []);
6565

6666
if ($groups instanceof GroupSequence) {
67-
$validator->atPath('data')->validate($form->getData(), $constraints, $groups);
68-
// Otherwise validate a constraint only once for the first
69-
// matching group
70-
foreach ($groups as $group) {
71-
if (\in_array($group, $formConstraint->groups)) {
72-
$validator->atPath('data')->validate($form->getData(), $formConstraint, $group);
73-
if (\count($this->context->getViolations()) > 0) {
74-
break;
67+
// Validate the data, the form AND nested fields in sequence
68+
$violationsCount = $this->context->getViolations()->count();
69+
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
70+
$hasChildren = $form->count() > 0;
71+
$this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null;
72+
73+
foreach ($groups->groups as $group) {
74+
if ($validateDataGraph) {
75+
$validator->atPath('data')->validate($data, null, $group);
76+
}
77+
78+
if ($groupedConstraints = self::getConstraintsInGroups($constraints, $group)) {
79+
$validator->atPath('data')->validate($data, $groupedConstraints, $group);
80+
}
81+
82+
foreach ($form->all() as $field) {
83+
if ($field->isSubmitted()) {
84+
// remember to validate this field is one group only
85+
// otherwise resolving the groups would reuse the same
86+
// sequence recursively, thus some fields could fail
87+
// in different steps without breaking early enough
88+
$this->resolvedGroups[$field] = (array) $group;
89+
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
7590
}
7691
}
92+
93+
if ($violationsCount < $this->context->getViolations()->count()) {
94+
break;
95+
}
96+
}
97+
98+
if ($hasChildren) {
99+
// destroy storage at the end of the sequence to avoid memory leaks
100+
$this->resolvedGroups = null;
77101
}
78102
} else {
103+
if ($validateDataGraph) {
104+
$validator->atPath('data')->validate($data, null, $groups);
105+
}
106+
79107
$groupedConstraints = [];
80108

81109
foreach ($constraints as $constraint) {
82110
// For the "Valid" constraint, validate the data in all groups
83111
if ($constraint instanceof Valid) {
84-
$validator->atPath('data')->validate($form->getData(), $constraint, $groups);
112+
$validator->atPath('data')->validate($data, $constraint, $groups);
85113

86114
continue;
87115
}
@@ -101,7 +129,7 @@ public function validate($form, Constraint $formConstraint)
101129
}
102130

103131
foreach ($groupedConstraints as $group => $constraint) {
104-
$validator->atPath('data')->validate($form->getData(), $constraint, $group);
132+
$validator->atPath('data')->validate($data, $constraint, $group);
105133
}
106134
}
107135
} elseif (!$form->isSynchronized()) {
@@ -160,7 +188,7 @@ public function validate($form, Constraint $formConstraint)
160188
*
161189
* @return string|GroupSequence|(string|GroupSequence)[] The validation groups
162190
*/
163-
private static function getValidationGroups(FormInterface $form)
191+
private function getValidationGroups(FormInterface $form)
164192
{
165193
// Determine the clicked button of the complete form tree
166194
$clickedButton = null;
@@ -184,6 +212,10 @@ private static function getValidationGroups(FormInterface $form)
184212
return self::resolveValidationGroups($groups, $form);
185213
}
186214

215+
if (isset($this->resolvedGroups[$form])) {
216+
return $this->resolvedGroups[$form];
217+
}
218+
187219
$form = $form->getParent();
188220
} while (null !== $form);
189221

@@ -209,4 +241,11 @@ private static function resolveValidationGroups($groups, FormInterface $form)
209241

210242
return (array) $groups;
243
}
244+
245+
private static function getConstraintsInGroups($constraints, $group)
246+
{
247+
return array_filter($constraints, static function (Constraint $constraint) use ($group) {
248+
return \in_array($group, $constraint->groups, true);
249+
});
250+
}
212251
}

src/Symfony/Component/Form/Resources/config/validation.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<class name="Symfony\Component\Form\Form">
88
<constraint name="Symfony\Component\Form\Extension\Validator\Constraints\Form" />
99
<property name="children">
10-
<constraint name="Valid" />
10+
<constraint name="Valid" />
1111
</property>
1212
</class>
1313
</constraint-mapping>

src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,8 @@ public function testHandleGroupSequenceValidationGroups()
444444
$form = $this->getCompoundForm($object, $options);
445445
$form->submit([]);
446446

447-
$this->expectValidateAt(0, 'data', $object, new GroupSequence(['group1', 'group2']));
448-
$this->expectValidateAt(1, 'data', $object, new GroupSequence(['group1', 'group2']));
447+
$this->expectValidateAt(0, 'data', $object, 'group1');
448+
$this->expectValidateAt(1, 'data', $object, 'group2');
449449

450450
$this->validator->validate($form, new Form());
451451

@@ -801,6 +801,39 @@ public function testCompositeConstraintValidatedInEachGroup()
801801
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
802802
}
803803

804+
public function testCompositeConstraintValidatedInSequence()
805+
{
806+
$form = $this->getCompoundForm([], [
807+
'constraints' => [
808+
new Collection([
809+
'field1' => new NotBlank([
810+
'groups' => ['field1'],
811+
]),
812+
'field2' => new NotBlank([
813+
'groups' => ['field2'],
814+
]),
815+
]),
816+
],
817+
'validation_groups' => new GroupSequence(['field1', 'field2']),
818+
])
819+
->add($this->getForm('field1'))
820+
->add($this->getForm('field2'))
821+
;
822+
823+
$form->submit([
824+
'field1' => '',
825+
'field2' => '',
826+
]);
827+
828+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
829+
$this->validator->initialize($context);
830+
$this->validator->validate($form, new Form());
831+
832+
$this->assertCount(1, $context->getViolations());
833+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
834+
$this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
835+
}
836+
804837
protected function createValidator()
805838
{
806839
return new FormValidator();
@@ -823,7 +856,7 @@ private function getForm($name = 'name', $dataClass = null, array $options = [])
823856

824857
private function getCompoundForm($data, array $options = [])
825858
{
826-
return $this->getBuilder('name', \get_class($data), $options)
859+
return $this->getBuilder('name', \is_object($data) ? \get_class($data) : null, $options)
827860
->setData($data)
828861
->setCompound(true)
829862
->setDataMapper(new PropertyPathMapper())

src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@
1212
namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
1313

1414
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
15+
use Symfony\Component\Form\Form;
1516
use Symfony\Component\Form\Forms;
1617
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
1718
use Symfony\Component\Form\Tests\Extension\Core\Type\FormTypeTest;
1819
use Symfony\Component\Form\Tests\Extension\Core\Type\TextTypeTest;
19-
use Symfony\Component\Validator\Constraints\Email;
20+
use Symfony\Component\Form\Tests\Fixtures\Author;
2021
use Symfony\Component\Validator\Constraints\GroupSequence;
2122
use Symfony\Component\Validator\Constraints\Length;
23+
use Symfony\Component\Validator\Constraints\NotBlank;
2224
use Symfony\Component\Validator\Constraints\Valid;
2325
use Symfony\Component\Validator\ConstraintViolationList;
26+
use Symfony\Component\Validator\Mapping\ClassMetadata;
27+
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
2428
use Symfony\Component\Validator\Validation;
2529

2630
class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
@@ -64,14 +68,71 @@ public function testGroupSequenceWithConstraintsOption()
6468
->add('field', TextTypeTest::TESTED_TYPE, [
6569
'constraints' => [
6670
new Length(['min' => 10, 'allowEmptyString' => true, 'groups' => ['First']]),
67-
new Email(['groups' => ['Second']]),
71+
new NotBlank(['groups' => ['Second']]),
6872
],
6973
])
7074
;
7175

7276
$form->submit(['field' => 'wrong']);
7377

74-
$this->assertCount(1, $form->getErrors(true));
78+
$errors = $form->getErrors(true);
79+
80+
$this->assertCount(1, $errors);
81+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
82+
}
83+
84+
public function testManyFieldsGroupSequenceWithConstraintsOption()
85+
{
86+
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
87+
88+
$formMetadata = new ClassMetadata(Form::class);
89+
$authorMetadata = (new ClassMetadata(Author::class))
90+
->addPropertyConstraint('firstName', new NotBlank(['groups' => 'Second']))
91+
;
92+
$metadataFactory = $this->createMock(MetadataFactoryInterface::class);
93+
$metadataFactory->expects($this->any())
94+
->method('getMetadataFor')
95+
->willReturnCallback(static function ($classOrObject) use ($formMetadata, $authorMetadata) {
96+
if (Author::class === $classOrObject || $classOrObject instanceof Author) {
97+
return $authorMetadata;
98+
}
99+
100+
if (Form::class === $classOrObject || $classOrObject instanceof Form) {
101+
return $formMetadata;
102+
}
103+
104+
return new ClassMetadata(\is_string($classOrObject) ? $classOrObject : \get_class($classOrObject));
105+
})
106+
;
107+
108+
$validator = Validation::createValidatorBuilder()
109+
->setMetadataFactory($metadataFactory)
110+
->getValidator()
111+
;
112+
$form = Forms::createFormFactoryBuilder()
113+
->addExtension(new ValidatorExtension($validator))
114+
->getFormFactory()
115+
->create(FormTypeTest::TESTED_TYPE, new Author(), (['validation_groups' => new GroupSequence(['First', 'Second'])]))
116+
->add('firstName', TextTypeTest::TESTED_TYPE)
117+
->add('lastName', TextTypeTest::TESTED_TYPE, [
118+
'constraints' => [
119+
new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString),
120+
],
121+
])
122+
->add('australian', TextTypeTest::TESTED_TYPE, [
123+
'constraints' => [
124+
new NotBlank(['groups' => ['Second']]),
125+
],
126+
])
127+
;
128+
129+
$form->submit(['firstName' => '', 'lastName' => 'wrong_1', 'australian' => '']);
130+
131+
$errors = $form->getErrors(true);
132+
133+
$this->assertCount(1, $errors);
134+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
135+
$this->assertSame('children[lastName].data', $errors[0]->getCause()->getPropertyPath());
75136
}
76137

77138
protected function createForm(array $options = [])

0 commit comments

Comments
 (0)
0