8000 [Validator] Add the `Charset` constraint · symfony/symfony@e084246 · GitHub
[go: up one dir, main page]

Skip to content

Commit e084246

Browse files
[Validator] Add the Charset constraint
1 parent 82dc312 commit e084246

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `list` and `associative_array` types to `Type` constraint
8+
* Add the `Charset` constraint
89

910
7.0
1011
---
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
* @author Alexandre Daubois <alex.daubois@gmail.com>
19+
*/
20+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
21+
final class Charset extends Constraint
22+
{
23+
public const BAD_ENCODING_ERROR = '94c5e58b-f892-4e25-8fd6-9d89c80bfe81';
24+
25+
protected const ERROR_NAMES = [
26+
self::BAD_ENCODING_ERROR => 'BAD_ENCODING_ERROR',
27+
];
28+
29+
public array|string $encodings = [];
30+
public string $message = 'The detected encoding "{{ detected }}" does not match one of the accepted encoding: "{{ encodings }}".';
31+
32+
public function __construct(array|string $encodings = null, string $message = null, array $groups = null, mixed $payload = null, array $options = null)
33+
{
34+
parent::__construct($options, $groups, $payload);
35+
36+
$this->message = $message ?? $this->message;
37+
$this->encodings = (array) ($encodings ?? $this->encodings);
38+
39+
if ([] === $this->encodings) {
40+
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires at least one encoding.', static::class));
41+
}
42+
}
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* @author Alexandre Daubois <alex.daubois@gmail.com>
21+
*/
22+
final class CharsetValidator extends ConstraintValidator
23+
{
24+
public function validate(mixed $value, Constraint $constraint): void
25+
{
26+
if (!$constraint instanceof Charset) {
27+
throw new UnexpectedTypeException($constraint, Charset::class);
28+
}
29+
30+
if (null === $value) {
31+
return;
32+
}
33+
34+
if (!\is_string($value)) {
35+
throw new UnexpectedValueException($value, 'string');
36+
}
37+
38+
if (!\in_array($detected = mb_detect_encoding($value, $constraint->encodings, true), $constraint->encodings, true)) {
39+
$this->context->buildViolation($constraint->message)
40+
->setParameter('{{ detected }}', $detected)
41+
->setParameter('{{ encodings }}', implode('", "', $constraint->encodings))
42+
->setCode(Charset::BAD_ENCODING_ERROR)
43+
->addViolation();
44+
}
45+
}
46+
}
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+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\Charset;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
use Symfony\Component\Validator\Mapping\ClassMetadata;
18+
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
19+
20+
class CharsetTest extends TestCase
21+
{
22+
public function testSingleEncodingCanBeSet()
23+
{
24+
$encoding = new Charset('UTF-8');
25+
26+
$this->assertSame(['UTF-8'], $encoding->encodings);
27+
}
28+
29+
public function testMultipleEncodingCanBeSet()
30+
{
31+
$encoding = new Charset(['ASCII', 'UTF-8']);
32+
33+
$this->assertSame(['ASCII', 'UTF-8'], $encoding->encodings);
34+
}
35+
36+
public function testThrowsOnNoCharset()
37+
{
38+
$this->expectException(ConstraintDefinitionException::class);
39+
$this->expectExceptionMessage('The "Symfony\Component\Validator\Constraints\Charset" constraint requires at least one encoding.');
40+
41+
new Charset();
42+
}
43+
44+
public function testAttributes()
45+
{
46+
$metadata = new ClassMetadata(CharsetDummy::class);
47+
$loader = new AttributeLoader();
48+
$this->assertTrue($loader->loadClassMetadata($metadata));
49+
50+
[$aConstraint] = $metadata->properties['a']->getConstraints();
51+
$this->assertSame(['UTF-8'], $aConstraint->encodings);
52+
53+
[$bConstraint] = $metadata->properties['b']->getConstraints();
54+
$this->assertSame(['ASCII', 'UTF-8'], $bConstraint->encodings);
55+
}
56+
}
57+
58+
class CharsetDummy
59+
{
60+
#[Charset('UTF-8')]
61+
private string $a;
62+
63+
#[Charset(['ASCII', 'UTF-8'])]
64+
private string $b;
65+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\Charset;
15+
use Symfony\Component\Validator\Constraints\CharsetValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
17+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
18+
19+
class CharsetValidatorTest extends ConstraintValidatorTestCase
20+
{
21+
protected function createValidator(): CharsetValidator
22+
{
23+
return new CharsetValidator();
24+
}
25+
26+
/**
27+
* @dataProvider provideValidValues
28+
*/
29+
public function testEncodingIsValid(string $value, array $encodings)
30+
{
31+
$this->validator->validate($value, new Charset(encodings: $encodings));
32+
33+
$this->assertNoViolation();
34+
}
35+
36+
/**
37+
* @dataProvider provideInvalidValues
38+
*/
39+
public function testInvalidValues(string $value, array $encodings)
40+
{
41+
$this->validator->validate($value, new Charset(encodings: $encodings));
42+
43+
$this->buildViolation('The detected encoding "{{ detected }}" does not match one of the accepted encoding: "{{ encodings }}".')
44+
->setParameter('{{ detected }}', mb_detect_encoding($value, $encodings, true))
45+
->setParameter('{{ encodings }}', implode(', ', $encodings))
46+
->setCode(Charset::BAD_ENCODING_ERROR)
47+
->assertRaised();
48+
}
49+
50+
/**
51+
* @dataProvider provideInvalidTypes
52+
*/
53+
public function testNonStringValues(mixed $value)
54+
{
55+
$this->expectException(UnexpectedValueException::class);
56+
$this->expectExceptionMessageMatches('/Expected argument of type "string", ".*" given/');
57+
58+
$this->validator->validate($value, new Charset(encodings: ['UTF-8']));
59+
}
60+
61+
public static function provideValidValues()
62+
{
63+
yield ['my ascii string', ['ASCII']];
64+
yield ['my ascii string', ['UTF-8']];
65+
yield ['my ascii string', ['ASCII', 'UTF-8']];
66+
yield ['my ûtf 8', ['ASCII', 'UTF-8']];
67+
yield ['my ûtf 8', ['UTF-8']];
68+
yield ['ώ', ['UTF-16']];
69+
}
70+
71+
public static function provideInvalidValues()
72+
{
73+
yield ['my non-Äscîi string', ['ASCII']];
74+
yield ['😊', ['7bit']];
75+
}
76+
77+
public static function provideInvalidTypes()
78+
{
79+
yield [true];
80+
yield [false];
81+
yield [1];
82+
yield [1.1];
83+
yield [[]];
84+
yield [new \stdClass()];
85+
}
86+
}

0 commit comments

Comments
 (0)
0