Skip to content

Commit 9cf7cbd

Browse files
committed
[Validator] Added StrictTypes as class constraint with not nullable typed properties
1 parent e493e9d commit 9cf7cbd

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.1.0
55
-----
66

7+
* added a `StrictTypes` constraint to ease validating non nullable typed properties
78
* added a `Cascade` constraint to ease validating typed nested objects
89
* added the `Hostname` constraint and validator
910
* added the `alpha3` option to the `Country` and `Language` constraints
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
16+
17+
/**
18+
* @Annotation
19+
* @Target({"CLASS"})
20+
*
21+
* @author Jules Pietri <jules@heahprod.com>
22+
*/
23+
class StrictTypes extends Constraint
24+
{
25+
public function __construct($options = null)
26+
{
27+
if (\is_array($options) && \array_key_exists('groups', $options)) {
28+
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
29+
}
30+
31+
parent::__construct($options);
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function getTargets()
38+
{
39+
return self::CLASS_CONSTRAINT;
40+
}
41+
}

src/Symfony/Component/Validator/Mapping/ClassMetadata.php

+17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Validator\Constraint;
1515
use Symfony\Component\Validator\Constraints\Cascade;
1616
use Symfony\Component\Validator\Constraints\GroupSequence;
17+
use Symfony\Component\Validator\Constraints\NotNull;
18+
use Symfony\Component\Validator\Constraints\StrictTypes;
1719
use Symfony\Component\Validator\Constraints\Traverse;
1820
use Symfony\Component\Validator\Constraints\Valid;
1921
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@@ -220,6 +222,21 @@ public function addConstraint(Constraint $constraint)
220222
return $this;
221223
}
222224

225+
if ($constraint instanceof StrictTypes) {
226+
if (\PHP_VERSION_ID < 70400) {
227+
throw new ConstraintDefinitionException(sprintf('The constraint "%s" requires PHP 7.4.', StrictTypes::class));
228+
}
229+
230+
foreach ($this->getReflectionClass()->getProperties() as $property) {
231+
if ($property->hasType() && !$property->getType()->allowsNull()) {
232+
$this->addPropertyConstraint($property->getName(), new NotNull());
233+
}
234+
}
235+
236+
// The constraint is not added
237+
return $this;
238+
}
239+
223240
$constraint->addImplicitGroupName($this->getDefaultGroup());
224241

225242
parent::addConstraint($constraint);

src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php

+31
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Validator\Constraint;
1616
use Symfony\Component\Validator\Constraints\Cascade;
17+
use Symfony\Component\Validator\Constraints\StrictTypes;
1718
use Symfony\Component\Validator\Constraints\Valid;
1819
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1920
use Symfony\Component\Validator\Mapping\CascadingStrategy;
@@ -346,4 +347,34 @@ public function testCascadeConstraint()
346347
'children',
347348
], $metadata->getConstrainedProperties());
348349
}
350+
351+
/**
352+
* @requires PHP < 7.4
353+
*/
354+
public function testStrictTypesConstraintIsNotAvailable()
355+
{
356+
$metadata = new ClassMetadata(CascadingEntity::class);
357+
358+
$this->expectException(ConstraintDefinitionException::class);
359+
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Constraints\StrictTypes" requires PHP 7.4.');
360+
361+
$metadata->addConstraint(new StrictTypes());
362+
}
363+
364+
/**
365+
* @requires PHP 7.4
366+
*/
367+
public function testStrictTypesConstraint()
368+
{
369+
$metadata = new ClassMetadata(CascadingEntity::class);
370+
371+
$metadata->addConstraint(new StrictTypes());
372+
373+
$this->assertCount(3, $metadata->properties);
374+
$this->assertSame([
375+
'scalar',
376+
'requiredChild',
377+
'children',
378+
], $metadata->getConstrainedProperties());
379+
}
349380
}

src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php

+43
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Validator\Constraints\Length;
2121
use Symfony\Component\Validator\Constraints\NotBlank;
2222
use Symfony\Component\Validator\Constraints\NotNull;
23+
use Symfony\Component\Validator\Constraints\StrictTypes;
2324
use Symfony\Component\Validator\Constraints\Type;
2425
use Symfony\Component\Validator\Constraints\Valid;
2526
use Symfony\Component\Validator\ConstraintValidatorFactory;
@@ -276,4 +277,46 @@ public function testValidateWithExplicitCascade()
276277

277278
CascadingEntity::$staticChild = null;
278279
}
280+
281+
/**
282+
* @requires PHP 7.4
283+
*/
284+
public function testValidateWithExplicitCascadeUninitialized()
285+
{
286+
$this->metadataFactory->addMetadata((new ClassMetadata(CascadingEntity::class))
287+
->addConstraint(new Cascade())
288+
);
289+
$this->metadataFactory->addMetadata((new ClassMetadata(CascadedChild::class))
290+
->addPropertyConstraint('name', new NotNull())
291+
);
292+
293+
$entity = new CascadingEntity();
294+
295+
$violations = $this->validator->validate($entity);
296+
297+
$this->assertCount(0, $violations);
298+
}
299+
300+
/**
301+
* @requires PHP 7.4
302+
*/
303+
public function testValidateWithExplicitStrictTypesUninitialized()
304+
{
305+
$this->metadataFactory->addMetadata((new ClassMetadata(CascadingEntity::class))
306+
->addConstraint(new StrictTypes())
307+
);
308+
309+
$entity = new CascadingEntity();
310+
311+
$violations = $this->validator->validate($entity);
312+
313+
$this->assertCount(3, $violations);
314+
315+
$this->assertInstanceOf(NotNull::class, $violations->get(0)->getConstraint());
316+
$this->assertInstanceOf(NotNull::class, $violations->get(1)->getConstraint());
317+
$this->assertInstanceOf(NotNull::class, $violations->get(2)->getConstraint());
318+
$this->assertSame('scalar', $violations->get(0)->getPropertyPath());
319+
$this->assertSame('requiredChild', $violations->get(1)->getPropertyPath());
320+
$this->assertSame('children', $violations->get(2)->getPropertyPath());
321+
}
279322
}

0 commit comments

Comments
 (0)