Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit21544c0

Browse files
[Validator] Add theWordCount constraint
1 parent1a16ebc commit21544c0

File tree

5 files changed

+347
-0
lines changed

5 files changed

+347
-0
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add the`Yaml` constraint for validating YAML content
1010
* Add`errorPath` to Unique constraint
1111
* Add the`format` option to the`Ulid` constraint to allow accepting different ULID formats
12+
* Add the`WordCount` constraint
1213

1314
7.1
1415
---
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
namespaceSymfony\Component\Validator\Constraints;
13+
14+
useSymfony\Component\Validator\Attribute\HasNamedArguments;
15+
useSymfony\Component\Validator\Constraint;
16+
useSymfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
useSymfony\Component\Validator\Exception\MissingOptionsException;
18+
19+
/**
20+
* @author Alexandre Daubois <alex.daubois@gmail.com>
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
23+
finalclass WordCountextends Constraint
24+
{
25+
publicconstTOO_SHORT_ERROR ='cc4925df-b5a6-42dd-87f3-21919f349bf3';
26+
publicconstTOO_LONG_ERROR ='a951a642-f662-4fad-8761-79250eef74cb';
27+
28+
protectedconstERROR_NAMES = [
29+
self::TOO_SHORT_ERROR =>'TOO_SHORT_ERROR',
30+
self::TOO_LONG_ERROR =>'TOO_LONG_ERROR',
31+
];
32+
33+
#[HasNamedArguments]
34+
publicfunction__construct(
35+
public ?int$min =null,
36+
public ?int$max =null,
37+
public ?string$locale =null,
38+
publicstring$minMessage ='This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.',
39+
publicstring$maxMessage ='This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.',
40+
?array$groups =null,
41+
mixed$payload =null,
42+
) {
43+
if (!class_exists(\IntlBreakIterator::class)) {
44+
thrownew \RuntimeException(\sprintf('The "%s" constraint requires the "intl" PHP extension.',__CLASS__));
45+
}
46+
47+
if (null ===$min &&null ===$max) {
48+
thrownewMissingOptionsException(\sprintf('Either option "min" or "max" must be given for constraint "%s".',__CLASS__), ['min','max']);
49+
}
50+
51+
if (null !==$min &&$min <0) {
52+
thrownewConstraintDefinitionException(\sprintf('The "%s" constraint requires the min word count to be a positive integer or 0 if set.',__CLASS__));
53+
}
54+
55+
if (null !==$max &&$max <=0) {
56+
thrownewConstraintDefinitionException(\sprintf('The "%s" constraint requires the max word count to be a positive integer if set.',__CLASS__));
57+
}
58+
59+
if (null !==$min &&null !==$max &&$min >$max) {
60+
thrownewConstraintDefinitionException(\sprintf('The "%s" constraint requires the min word count to be less than or equal to the max word count.',__CLASS__));
61+
}
62+
63+
parent::__construct(null,$groups,$payload);
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
namespaceSymfony\Component\Validator\Constraints;
13+
14+
useSymfony\Component\Validator\Constraint;
15+
useSymfony\Component\Validator\ConstraintValidator;
16+
useSymfony\Component\Validator\Exception\UnexpectedTypeException;
17+
useSymfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* @author Alexandre Daubois <alex.daubois@gmail.com>
21+
*/
22+
finalclass WordCountValidatorextends ConstraintValidator
23+
{
24+
publicfunctionvalidate(mixed$value,Constraint$constraint):void
25+
{
26+
if (!class_exists(\IntlBreakIterator::class)) {
27+
thrownew \RuntimeException(\sprintf('The "%s" constraint requires the "intl" PHP extension.',__CLASS__));
28+
}
29+
30+
if (!$constraintinstanceof WordCount) {
31+
thrownewUnexpectedTypeException($constraint, WordCount::class);
32+
}
33+
34+
if (null ===$value ||'' ===$value) {
35+
return;
36+
}
37+
38+
if (!\is_string($value) && !$valueinstanceof \Stringable) {
39+
thrownewUnexpectedValueException($value,'string');
40+
}
41+
42+
$iterator = \IntlBreakIterator::createWordInstance($constraint->locale);
43+
$iterator->setText($value);
44+
$words =iterator_to_array($iterator->getPartsIterator());
45+
46+
// erase "blank words" and don't count them as words
47+
$wordsCount =\count(array_filter(array_map(trim(...),$words)));
48+
49+
if (null !==$constraint->min &&$wordsCount <$constraint->min) {
50+
$this->context->buildViolation($constraint->minMessage)
51+
->setParameter('{{ count }}',$wordsCount)
52+
->setParameter('{{ min }}',$constraint->min)
53+
->setPlural($constraint->min)
54+
->setInvalidValue($value)
55+
->addViolation();
56+
}elseif (null !==$constraint->max &&$wordsCount >$constraint->max) {
57+
$this->context->buildViolation($constraint->maxMessage)
58+
->setParameter('{{ count }}',$wordsCount)
59+
->setParameter('{{ max }}',$constraint->max)
60+
->setPlural($constraint->max)
61+
->setInvalidValue($value)
62+
->addViolation();
63+
}
64+
}
65+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
namespaceSymfony\Component\Validator\Tests\Constraints;
13+
14+
usePHPUnit\Framework\TestCase;
15+
useSymfony\Component\Validator\Constraints\WordCount;
16+
useSymfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
useSymfony\Component\Validator\Exception\MissingOptionsException;
18+
useSymfony\Component\Validator\Mapping\ClassMetadata;
19+
useSymfony\Component\Validator\Mapping\Loader\AttributeLoader;
20+
21+
class WordCountTestextends TestCase
22+
{
23+
publicfunctiontestLocaleIsSet()
24+
{
25+
$wordCount =newWordCount(min:1, locale:'en');
26+
27+
$this->assertSame('en',$wordCount->locale);
28+
}
29+
30+
publicfunctiontestOnlyMinIsSet()
31+
{
32+
$wordCount =newWordCount(1);
33+
34+
$this->assertSame(1,$wordCount->min);
35+
$this->assertNull($wordCount->max);
36+
$this->assertNull($wordCount->locale);
37+
}
38+
39+
publicfunctiontestOnlyMaxIsSet()
40+
{
41+
$wordCount =newWordCount(max:1);
42+
43+
$this->assertNull($wordCount->min);
44+
$this->assertSame(1,$wordCount->max);
45+
$this->assertNull($wordCount->locale);
46+
}
47+
48+
publicfunctiontestMinMustBeNatural()
49+
{
50+
$this->expectException(ConstraintDefinitionException::class);
51+
$this->expectExceptionMessage('The "Symfony\Component\Validator\Constraints\WordCount" constraint requires the min word count to be a positive integer or 0 if set.');
52+
53+
newWordCount(-1);
54+
}
55+
56+
publicfunctiontestMaxMustBePositive()
57+
{
58+
$this->expectException(ConstraintDefinitionException::class);
59+
$this->expectExceptionMessage('The "Symfony\Component\Validator\Constraints\WordCount" constraint requires the max word count to be a positive integer if set.');
60+
61+
newWordCount(max:0);
62+
}
63+
64+
publicfunctiontestNothingIsSet()
65+
{
66+
$this->expectException(MissingOptionsException::class);
67+
$this->expectExceptionMessage('Either option "min" or "max" must be given for constraint "Symfony\Component\Validator\Constraints\WordCount".');
68+
69+
newWordCount();
70+
}
71+
72+
publicfunctiontestMaxIsLessThanMin()
73+
{
74+
$this->expectException(ConstraintDefinitionException::class);
75+
$this->expectExceptionMessage('The "Symfony\Component\Validator\Constraints\WordCount" constraint requires the min word count to be less than or equal to the max word count.');
76+
77+
newWordCount(2,1);
78+
}
79+
80+
publicfunctiontestMinAndMaxAreEquals()
81+
{
82+
$wordCount =newWordCount(1,1);
83+
84+
$this->assertSame(1,$wordCount->min);
85+
$this->assertSame(1,$wordCount->max);
86+
$this->assertNull($wordCount->locale);
87+
}
88+
89+
publicfunctiontestAttributes()
90+
{
91+
$metadata =newClassMetadata(WordCountDummy::class);
92+
$loader =newAttributeLoader();
93+
$this->assertTrue($loader->loadClassMetadata($metadata));
94+
95+
[$aConstraint] =$metadata->properties['a']->getConstraints();
96+
$this->assertSame(1,$aConstraint->min);
97+
$this->assertSame(null,$aConstraint->max);
98+
$this->assertNull($aConstraint->locale);
99+
100+
[$bConstraint] =$metadata->properties['b']->getConstraints();
101+
$this->assertSame(2,$bConstraint->min);
102+
$this->assertSame(5,$bConstraint->max);
103+
$this->assertNull($bConstraint->locale);
104+
105+
[$cConstraint] =$metadata->properties['c']->getConstraints();
106+
$this->assertSame(3,$cConstraint->min);
107+
$this->assertNull($cConstraint->max);
108+
$this->assertSame('en',$cConstraint->locale);
109+
}
110+
}
111+
112+
class WordCountDummy
113+
{
114+
#[WordCount(min:1)]
115+
privatestring$a;
116+
117+
#[WordCount(min:2, max:5)]
118+
privatestring$b;
119+
120+
#[WordCount(min:3, locale:'en')]
121+
privatestring$c;
122+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
namespaceSymfony\Component\Validator\Tests\Constraints;
13+
14+
useSymfony\Component\Validator\Constraints\WordCount;
15+
useSymfony\Component\Validator\Constraints\WordCountValidator;
16+
useSymfony\Component\Validator\Exception\UnexpectedValueException;
17+
useSymfony\Component\Validator\Test\ConstraintValidatorTestCase;
18+
useSymfony\Component\Validator\Tests\Constraints\Fixtures\StringableValue;
19+
20+
class WordCountValidatorTestextends ConstraintValidatorTestCase
21+
{
22+
protectedfunctioncreateValidator():WordCountValidator
23+
{
24+
returnnewWordCountValidator();
25+
}
26+
27+
/**
28+
* @dataProvider provideValidValues
29+
*/
30+
publicfunctiontestValidWordCount(string|\Stringable|null$value,int$expectedWordCount)
31+
{
32+
$this->validator->validate($value,newWordCount(min:$expectedWordCount, max:$expectedWordCount));
33+
34+
$this->assertNoViolation();
35+
}
36+
37+
publicfunctiontestTooShort()
38+
{
39+
$constraint =newWordCount(min:4, minMessage:'myMessage');
40+
$this->validator->validate('my ascii string',$constraint);
41+
42+
$this->buildViolation('myMessage')
43+
->setParameter('{{ count }}',3)
44+
->setParameter('{{ min }}',4)
45+
->setPlural(4)
46+
->setInvalidValue('my ascii string')
47+
->assertRaised();
48+
}
49+
50+
publicfunctiontestTooLong()
51+
{
52+
$constraint =newWordCount(max:3, maxMessage:'myMessage');
53+
$this->validator->validate('my beautiful ascii string',$constraint);
54+
55+
$this->buildViolation('myMessage')
56+
->setParameter('{{ count }}',4)
57+
->setParameter('{{ max }}',3)
58+
->setPlural(3)
59+
->setInvalidValue('my beautiful ascii string')
60+
->assertRaised();
61+
}
62+
63+
/**
64+
* @dataProvider provideInvalidTypes
65+
*/
66+
publicfunctiontestNonStringValues(mixed$value)
67+
{
68+
$this->expectException(UnexpectedValueException::class);
69+
$this->expectExceptionMessageMatches('/Expected argument of type "string", ".*" given/');
70+
71+
$this->validator->validate($value,newWordCount(min:1));
72+
}
73+
74+
publicstaticfunctionprovideValidValues()
75+
{
76+
yield ['my ascii string',3];
77+
yield [" with a\nnewline",3];
78+
yield ["皆さん、こんにちは。",4];
79+
yield ["你好,世界!这是一个测试。",9];
80+
yield [newStringableValue('my ûtf 8'),3];
81+
yield [null,1];// null should always pass and eventually be handled by NotNullValidator
82+
yield ['',1];// empty string should always pass and eventually be handled by NotBlankValidator
83+
}
84+
85+
publicstaticfunctionprovideInvalidTypes()
86+
{
87+
yield [true];
88+
yield [false];
89+
yield [1];
90+
yield [1.1];
91+
yield [[]];
92+
yield [new \stdClass()];
93+
}
94+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp