diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md
index 9864cecad5..87d49e1f1f 100644
--- a/UPGRADE-4.4.md
+++ b/UPGRADE-4.4.md
@@ -107,3 +107,7 @@ Validator
* Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`.
Pass it as the first argument instead.
+ * The `Length` constraint expects the `allowEmptyString` option to be defined
+ when the `min` option is used.
+ Set it to `true` to keep the current behavior and `false` to reject empty strings.
+ In 5.0, it'll become optional and will default to `false`.
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php
index abf8819a4c..50b5845581 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php
@@ -2,6 +2,9 @@
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+
/**
* Class BaseUser.
*/
@@ -46,4 +49,15 @@ class BaseUser
{
return $this->username;
}
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
+ $metadata->addPropertyConstraint('username', new Assert\Length([
+ 'min' => 2,
+ 'max' => 120,
+ 'groups' => ['Registration'],
+ ] + $allowEmptyString));
+ }
}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php
index dc06d37fa3..9a2111f2b9 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php
@@ -14,6 +14,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* @ORM\Entity
@@ -36,13 +37,11 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity
/**
* @ORM\Column(length=20)
- * @Assert\Length(min=5)
*/
public $mergedMaxLength;
/**
* @ORM\Column(length=20)
- * @Assert\Length(min=1, max=10)
*/
public $alreadyMappedMaxLength;
@@ -69,4 +68,12 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity
/** @ORM\Column(type="simple_array", length=100) */
public $simpleArrayField = [];
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
+ $metadata->addPropertyConstraint('mergedMaxLength', new Assert\Length(['min' => 5] + $allowEmptyString));
+ $metadata->addPropertyConstraint('alreadyMappedMaxLength', new Assert\Length(['min' => 1, 'max' => 10] + $allowEmptyString));
+ }
}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml
index bf64b92ca4..ddb8a13bc1 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml
+++ b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml
@@ -9,11 +9,6 @@
-
-
-
-
-
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php
index 2dcab2533d..45cae2da41 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php
@@ -40,6 +40,7 @@ class DoctrineLoaderTest extends TestCase
}
$validator = Validation::createValidatorBuilder()
+ ->addMethodMapping('loadValidatorMetadata')
->enableAnnotationMapping()
->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}'))
->getValidator()
@@ -142,6 +143,7 @@ class DoctrineLoaderTest extends TestCase
}
$validator = Validation::createValidatorBuilder()
+ ->addMethodMapping('loadValidatorMetadata')
->enableAnnotationMapping()
->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml'])
->addLoader(
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
index 57f92b6574..a920e3be5b 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
@@ -57,13 +57,15 @@ class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
public function testGroupSequenceWithConstraintsOption()
{
+ $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
$form = Forms::createFormFactoryBuilder()
->addExtension(new ValidatorExtension(Validation::createValidator()))
->getFormFactory()
->create(FormTypeTest::TESTED_TYPE, null, (['validation_groups' => new GroupSequence(['First', 'Second'])]))
->add('field', TextTypeTest::TESTED_TYPE, [
'constraints' => [
- new Length(['min' => 10, 'groups' => ['First']]),
+ new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString),
new Email(['groups' => ['Second']]),
],
])
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
index 878bbfad21..fd11342bea 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
@@ -61,11 +61,13 @@ class ValidatorTypeGuesserTest extends TestCase
public function guessRequiredProvider()
{
+ $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
return [
[new NotNull(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
[new NotBlank(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
[new IsTrue(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
- [new Length(10), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
+ [new Length(['min' => 10, 'max' => 10] + $allowEmptyString), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
[new Range(['min' => 1, 'max' => 20]), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
];
}
@@ -101,7 +103,9 @@ class ValidatorTypeGuesserTest extends TestCase
public function testGuessMaxLengthForConstraintWithMinValue()
{
- $constraint = new Length(['min' => '2']);
+ $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
+ $constraint = new Length(['min' => '2'] + $allowEmptyString);
$result = $this->guesser->guessMaxLengthForConstraint($constraint);
$this->assertNull($result);
diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md
index 8a85ee35ef..a86679dd1c 100644
--- a/src/Symfony/Component/Validator/CHANGELOG.md
+++ b/src/Symfony/Component/Validator/CHANGELOG.md
@@ -8,6 +8,7 @@ CHANGELOG
* added the `compared_value_path` parameter in violations when using any
comparison constraint with the `propertyPath` option.
* added support for checking an array of types in `TypeValidator`
+ * added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`.
4.3.0
-----
diff --git a/src/Symfony/Component/Validator/Constraints/Length.php b/src/Symfony/Component/Validator/Constraints/Length.php
index 0edd0e97e0..d9b0d1f1c5 100644
--- a/src/Symfony/Component/Validator/Constraints/Length.php
+++ b/src/Symfony/Component/Validator/Constraints/Length.php
@@ -41,6 +41,7 @@ class Length extends Constraint
public $min;
public $charset = 'UTF-8';
public $normalizer;
+ public $allowEmptyString;
public function __construct($options = null)
{
@@ -56,6 +57,13 @@ class Length extends Constraint
parent::__construct($options);
+ if (null === $this->allowEmptyString) {
+ $this->allowEmptyString = true;
+ if (null !== $this->min) {
+ @trigger_error(sprintf('Using the "%s" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false.', self::class), E_USER_DEPRECATED);
+ }
+ }
+
if (null === $this->min && null === $this->max) {
throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint %s', __CLASS__), ['min', 'max']);
}
diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php
index f3cf245cf4..b1b5d7c770 100644
--- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php
@@ -30,7 +30,7 @@ class LengthValidator extends ConstraintValidator
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Length');
}
- if (null === $value || '' === $value) {
+ if (null === $value || ('' === $value && $constraint->allowEmptyString)) {
return;
}
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php
index 6a20ff541f..b7aa2339aa 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php
@@ -21,7 +21,7 @@ class LengthTest extends TestCase
{
public function testNormalizerCanBeSet()
{
- $length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim']);
+ $length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim', 'allowEmptyString' => false]);
$this->assertEquals('trim', $length->normalizer);
}
@@ -32,7 +32,7 @@ class LengthTest extends TestCase
*/
public function testInvalidNormalizerThrowsException()
{
- new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable']);
+ new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable', 'allowEmptyString' => false]);
}
/**
@@ -41,6 +41,6 @@ class LengthTest extends TestCase
*/
public function testInvalidNormalizerObjectThrowsException()
{
- new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass()]);
+ new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass(), 'allowEmptyString' => false]);
}
}
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php
index 96c388ae5b..6e94a0233e 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php
@@ -22,26 +22,47 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
return new LengthValidator();
}
- public function testNullIsValid()
+ public function testLegacyNullIsValid()
{
- $this->validator->validate(null, new Length(6));
+ $this->validator->validate(null, new Length(['value' => 6, 'allowEmptyString' => false]));
$this->assertNoViolation();
}
- public function testEmptyStringIsValid()
+ /**
+ * @group legacy
+ * @expectedDeprecation Using the "Symfony\Component\Validator\Constraints\Length" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false.
+ */
+ public function testLegacyEmptyStringIsValid()
{
$this->validator->validate('', new Length(6));
$this->assertNoViolation();
}
+ public function testEmptyStringIsInvalid()
+ {
+ $this->validator->validate('', new Length([
+ 'value' => $limit = 6,
+ 'allowEmptyString' => false,
+ 'exactMessage' => 'myMessage',
+ ]));
+
+ $this->buildViolation('myMessage')
+ ->setParameter('{{ value }}', '""')
+ ->setParameter('{{ limit }}', $limit)
+ ->setInvalidValue('')
+ ->setPlural($limit)
+ ->setCode(Length::TOO_SHORT_ERROR)
+ ->assertRaised();
+ }
+
/**
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException
*/
public function testExpectsStringCompatibleType()
{
- $this->validator->validate(new \stdClass(), new Length(5));
+ $this->validator->validate(new \stdClass(), new Length(['value' => 5, 'allowEmptyString' => false]));
}
public function getThreeOrLessCharacters()
@@ -109,7 +130,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
*/
public function testValidValuesMin($value)
{
- $constraint = new Length(['min' => 5]);
+ $constraint = new Length(['min' => 5, 'allowEmptyString' => false]);
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
@@ -131,7 +152,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
*/
public function testValidValuesExact($value)
{
- $constraint = new Length(4);
+ $constraint = new Length(['value' => 4, 'allowEmptyString' => false]);
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
@@ -142,7 +163,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
*/
public function testValidNormalizedValues($value)
{
- $constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim']);
+ $constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim', 'allowEmptyString' => false]);
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
@@ -156,6 +177,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
$constraint = new Length([
'min' => 4,
'minMessage' => 'myMessage',
+ 'allowEmptyString' => false,
]);
$this->validator->validate($value, $constraint);
@@ -199,6 +221,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
'min' => 4,
'max' => 4,
'exactMessage' => 'myMessage',
+ 'allowEmptyString' => false,
]);
$this->validator->validate($value, $constraint);
@@ -221,6 +244,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
'min' => 4,
'max' => 4,
'exactMessage' => 'myMessage',
+ 'allowEmptyString' => false,
]);
$this->validator->validate($value, $constraint);
@@ -244,6 +268,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
'max' => 1,
'charset' => $charset,
'charsetMessage' => 'myMessage',
+ 'allowEmptyString' => false,
]);
$this->validator->validate($value, $constraint);
@@ -262,7 +287,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
public function testConstraintDefaultOption()
{
- $constraint = new Length(5);
+ $constraint = new Length(['value' => 5, 'allowEmptyString' => false]);
$this->assertEquals(5, $constraint->min);
$this->assertEquals(5, $constraint->max);
@@ -270,7 +295,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
public function testConstraintAnnotationDefaultOption()
{
- $constraint = new Length(['value' => 5, 'exactMessage' => 'message']);
+ $constraint = new Length(['value' => 5, 'exactMessage' => 'message', 'allowEmptyString' => false]);
$this->assertEquals(5, $constraint->min);
$this->assertEquals(5, $constraint->max);
diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php
index 8109b6b9bf..c0c7c3e96d 100644
--- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php
@@ -103,7 +103,7 @@ class RecursiveValidatorTest extends AbstractTest
public function testCollectionConstraintValidateAllGroupsForNestedConstraints()
{
$this->metadata->addPropertyConstraint('data', new Collection(['fields' => [
- 'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two'])],
+ 'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false])],
'two' => [new NotBlank(['groups' => 'two'])],
]]));
@@ -121,7 +121,7 @@ class RecursiveValidatorTest extends AbstractTest
{
$this->metadata->addPropertyConstraint('data', new All(['constraints' => [
new NotBlank(['groups' => 'one']),
- new Length(['min' => 2, 'groups' => 'two']),
+ new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false]),
]]));
$entity = new Entity();
@@ -129,8 +129,9 @@ class RecursiveValidatorTest extends AbstractTest
$violations = $this->validator->validate($entity, null, ['one', 'two']);
- $this->assertCount(2, $violations);
+ $this->assertCount(3, $violations);
$this->assertInstanceOf(NotBlank::class, $violations->get(0)->getConstraint());
$this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint());
+ $this->assertInstanceOf(Length::class, $violations->get(2)->getConstraint());
}
}