Skip to content

Commit 0edd0a5

Browse files
committed
Merge branch '6.4' into 7.0
* 6.4: fix compatibility with Twig 3.10 [Strings][EnglishInflector] Fix incorrect pluralisation of 'Album' handle union and intersection types for cascaded validations move wiring of the property info extractor to the ObjectNormalizer restore deprecated properties move Process component dep to require-dev Remove calls to `onConsecutiveCalls()` fix: remove unwanted type cast accept AbstractAsset instances when filtering schemas better distinguish URL schemes and windows drive letters handle edge cases when constructing constraints with named arguments convert empty CSV header names into numeric keys
2 parents ab4e75b + afc96ee commit 0edd0a5

14 files changed

+229
-7
lines changed

Mapping/ClassMetadata.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public function addConstraint(Constraint $constraint): static
198198
continue;
199199
}
200200

201-
if ($property->hasType() && (('array' === $type = $property->getType()->getName()) || class_exists($type))) {
201+
if ($this->canCascade($property->getType())) {
202202
$this->addPropertyConstraint($property->getName(), new Valid());
203203
}
204204
}
@@ -483,4 +483,33 @@ private function checkConstraint(Constraint $constraint): void
483483
}
484484
}
485485
}
486+
487+
private function canCascade(?\ReflectionType $type = null): bool
488+
{
489+
if (null === $type) {
490+
return false;
491+
}
492+
493+
if ($type instanceof \ReflectionIntersectionType) {
494+
foreach ($type->getTypes() as $nestedType) {
495+
if ($this->canCascade($nestedType)) {
496+
return true;
497+
}
498+
}
499+
500+
return false;
501+
}
502+
503+
if ($type instanceof \ReflectionUnionType) {
504+
foreach ($type->getTypes() as $nestedType) {
505+
if (!$this->canCascade($nestedType)) {
506+
return false;
507+
}
508+
}
509+
510+
return true;
511+
}
512+
513+
return $type instanceof \ReflectionNamedType && (\in_array($type->getName(), ['array', 'null'], true) || class_exists($type->getName()));
514+
}
486515
}

Mapping/Loader/AbstractLoader.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ protected function newConstraint(string $name, mixed $options = null): Constrain
8585
}
8686

8787
if ($this->namedArgumentsCache[$className] ??= (bool) (new \ReflectionMethod($className, '__construct'))->getAttributes(HasNamedArguments::class)) {
88+
if (null === $options) {
89+
return new $className();
90+
}
91+
92+
if (!\is_array($options)) {
93+
return new $className($options);
94+
}
95+
96+
if (1 === \count($options) && isset($options['value'])) {
97+
return new $className($options['value']);
98+
}
99+
88100
return new $className(...$options);
89101
}
90102

Mapping/Loader/XmlFileLoader.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
8080
foreach ($nodes as $node) {
8181
if (\count($node) > 0) {
8282
if (\count($node->value) > 0) {
83-
$options = $this->parseValues($node->value);
83+
$options = [
84+
'value' => $this->parseValues($node->value),
85+
];
8486
} elseif (\count($node->constraint) > 0) {
8587
$options = $this->parseConstraints($node->constraint);
8688
} elseif (\count($node->option) > 0) {
@@ -94,6 +96,10 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
9496
$options = null;
9597
}
9698

99+
if (isset($options['groups']) && !\is_array($options['groups'])) {
100+
$options['groups'] = (array) $options['groups'];
101+
}
102+
97103
$constraints[] = $this->newConstraint((string) $node['name'], $options);
98104
}
99105

Mapping/Loader/YamlFileLoader.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ protected function parseNodes(array $nodes): array
8686
$options = $this->parseNodes($options);
8787
}
8888

89+
if (null !== $options && (!\is_array($options) || array_is_list($options))) {
90+
$options = [
91+
'value' => $options,
92+
];
93+
}
94+
8995
$values[] = $this->newConstraint(key($childNodes), $options);
9096
} else {
9197
if (\is_array($childNodes)) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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\Validator\Tests\Fixtures;
13+
14+
class CascadingEntityIntersection
15+
{
16+
public CascadedChild&\stdClass $classes;
17+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Validator\Tests\Fixtures;
13+
14+
class CascadingEntityUnion
15+
{
16+
public CascadedChild|\stdClass $classes;
17+
public CascadedChild|array $classAndArray;
18+
public CascadedChild|null $classAndNull;
19+
public array|null $arrayAndNull;
20+
public CascadedChild|array|null $classAndArrayAndNull;
21+
public int|string $scalars;
22+
public int|null $scalarAndNull;
23+
public CascadedChild|int $classAndScalar;
24+
public array|int $arrayAndScalar;
25+
}

Tests/Mapping/ClassMetadataTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Symfony\Component\Validator\Mapping\CascadingStrategy;
2323
use Symfony\Component\Validator\Mapping\ClassMetadata;
2424
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntity;
25+
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntityIntersection;
26+
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntityUnion;
2527
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
2628
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
2729
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
@@ -342,6 +344,34 @@ public function testCascadeConstraint()
342344
], $metadata->getConstrainedProperties());
343345
}
344346

347+
public function testCascadeConstraintWithUnionTypeProperties()
348+
{
349+
$metadata = new ClassMetadata(CascadingEntityUnion::class);
350+
$metadata->addConstraint(new Cascade());
351+
352+
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy());
353+
$this->assertCount(5, $metadata->properties);
354+
$this->assertSame([
355+
'classes',
356+
'classAndArray',
357+
'classAndNull',
358+
'arrayAndNull',
359+
'classAndArrayAndNull',
360+
], $metadata->getConstrainedProperties());
361+
}
362+
363+
public function testCascadeConstraintWithIntersectionTypeProperties()
364+
{
365+
$metadata = new ClassMetadata(CascadingEntityIntersection::class);
366+
$metadata->addConstraint(new Cascade());
367+
368+
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy());
369+
$this->assertCount(1, $metadata->properties);
370+
$this->assertSame([
371+
'classes',
372+
], $metadata->getConstrainedProperties());
373+
}
374+
345375
public function testCascadeConstraintWithExcludedProperties()
346376
{
347377
$metadata = new ClassMetadata(CascadingEntity::class);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Validator\Tests\Mapping\Loader\Fixtures;
13+
14+
use Symfony\Component\Validator\Attribute\HasNamedArguments;
15+
use Symfony\Component\Validator\Constraint;
16+
17+
class ConstraintWithNamedArguments extends Constraint
18+
{
19+
public $choices;
20+
21+
#[HasNamedArguments]
22+
public function __construct(array|string|null $choices = [], ?array $groups = null)
23+
{
24+
parent::__construct([], $groups);
25+
26+
$this->choices = $choices;
27+
}
28+
29+
public function getTargets(): string
30+
{
31+
return self::CLASS_CONSTRAINT;
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\Validator\Tests\Mapping\Loader\Fixtures;
13+
14+
use Symfony\Component\Validator\Attribute\HasNamedArguments;
15+
use Symfony\Component\Validator\Constraint;
16+
17+
class ConstraintWithoutValueWithNamedArguments extends Constraint
18+
{
19+
#[HasNamedArguments]
20+
public function __construct(?array $groups = null)
21+
{
22+
parent::__construct([], $groups);
23+
}
24+
25+
public function getTargets(): string
26+
{
27+
return self::CLASS_CONSTRAINT;
28+
}
29+
}

Tests/Mapping/Loader/PropertyInfoLoaderTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ public function testLoadClassMetadata()
5454
'noAutoMapping',
5555
])
5656
;
57+
5758
$propertyInfoStub
5859
->method('getTypes')
59-
->will($this->onConsecutiveCalls(
60+
->willReturn(
6061
[new Type(Type::BUILTIN_TYPE_STRING, true)],
6162
[new Type(Type::BUILTIN_TYPE_STRING)],
6263
[new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_BOOL)],
@@ -69,11 +70,12 @@ public function testLoadClassMetadata()
6970
[new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))],
7071
[new Type(Type::BUILTIN_TYPE_STRING)],
7172
[new Type(Type::BUILTIN_TYPE_STRING)]
72-
))
73+
)
7374
;
75+
7476
$propertyInfoStub
7577
->method('isWritable')
76-
->will($this->onConsecutiveCalls(
78+
->willReturn(
7779
true,
7880
true,
7981
true,
@@ -86,7 +88,7 @@ public function testLoadClassMetadata()
8688
true,
8789
false,
8890
true
89-
))
91+
)
9092
;
9193

9294
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub, '{.*}');
@@ -221,9 +223,10 @@ public function testClassNoAutoMapping()
221223
->method('getProperties')
222224
->willReturn(['string', 'autoMappingExplicitlyEnabled'])
223225
;
226+
224227
$propertyInfoStub
225228
->method('getTypes')
226-
->willReturnOnConsecutiveCalls(
229+
->willReturn(
227230
[new Type(Type::BUILTIN_TYPE_STRING)],
228231
[new Type(Type::BUILTIN_TYPE_BOOL)]
229232
);

0 commit comments

Comments
 (0)