Skip to content

Commit b8ab23c

Browse files
committed
Merge branch '7.0' into 7.1
* 7.0: 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 8f89f35 + 0edd0a5 commit b8ab23c

14 files changed

+223
-4
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;
@@ -346,6 +348,34 @@ public function testCascadeConstraint()
346348
], $metadata->getConstrainedProperties());
347349
}
348350

351+
public function testCascadeConstraintWithUnionTypeProperties()
352+
{
353+
$metadata = new ClassMetadata(CascadingEntityUnion::class);
354+
$metadata->addConstraint(new Cascade());
355+
356+
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy());
357+
$this->assertCount(5, $metadata->properties);
358+
$this->assertSame([
359+
'classes',
360+
'classAndArray',
361+
'classAndNull',
362+
'arrayAndNull',
363+
'classAndArrayAndNull',
364+
], $metadata->getConstrainedProperties());
365+
}
366+
367+
public function testCascadeConstraintWithIntersectionTypeProperties()
368+
{
369+
$metadata = new ClassMetadata(CascadingEntityIntersection::class);
370+
$metadata->addConstraint(new Cascade());
371+
372+
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy());
373+
$this->assertCount(1, $metadata->properties);
374+
$this->assertSame([
375+
'classes',
376+
], $metadata->getConstrainedProperties());
377+
}
378+
349379
public function testCascadeConstraintWithExcludedProperties()
350380
{
351381
$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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public function getTypes(string $class, string $property, array $context = []):
114114
$propertyAccessExtractor = $this->createMock(PropertyAccessExtractorInterface::class);
115115
$propertyAccessExtractor
116116
->method('isWritable')
117-
->will($this->onConsecutiveCalls(
117+
->willReturn(
118118
true,
119119
true,
120120
true,
@@ -127,7 +127,7 @@ public function getTypes(string $class, string $property, array $context = []):
127127
true,
128128
false,
129129
true
130-
))
130+
)
131131
;
132132

133133
$propertyInfoLoader = new PropertyInfoLoader($propertyListExtractor, $propertyTypeExtractor, $propertyAccessExtractor, '{.*}');

0 commit comments

Comments
 (0)