Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #53154 [Validator] Add the
Charset
constraint (alexandre-da…
…ubois) This PR was merged into the 7.1 branch. Discussion ---------- [Validator] Add the `Charset` constraint | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Our use case: we receive some file contents in our DTOs that we only want to process if their encoding matches UTF-8 and reject the whole thing at validation otherwise. Commits ------- e084246 [Validator] Add the `Charset` constraint
- Loading branch information
Showing
5 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | ||
|
||
/** | ||
* @author Alexandre Daubois <alex.daubois@gmail.com> | ||
*/ | ||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] | ||
final class Charset extends Constraint | ||
{ | ||
public const BAD_ENCODING_ERROR = '94c5e58b-f892-4e25-8fd6-9d89c80bfe81'; | ||
|
||
protected const ERROR_NAMES = [ | ||
self::BAD_ENCODING_ERROR => 'BAD_ENCODING_ERROR', | ||
]; | ||
|
||
public array|string $encodings = []; | ||
public string $message = 'The detected encoding "{{ detected }}" does not match one of the accepted encoding: "{{ encodings }}".'; | ||
|
||
public function __construct(array|string $encodings = null, string $message = null, array $groups = null, mixed $payload = null, array $options = null) | ||
{ | ||
parent::__construct($options, $groups, $payload); | ||
|
||
$this->message = $message ?? $this->message; | ||
$this->encodings = (array) ($encodings ?? $this->encodings); | ||
|
||
if ([] === $this->encodings) { | ||
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires at least one encoding.', static::class)); | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/Symfony/Component/Validator/Constraints/CharsetValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
|
||
/** | ||
* @author Alexandre Daubois <alex.daubois@gmail.com> | ||
*/ | ||
final class CharsetValidator extends ConstraintValidator | ||
{ | ||
public function validate(mixed $value, Constraint $constraint): void | ||
{ | ||
if (!$constraint instanceof Charset) { | ||
throw new UnexpectedTypeException($constraint, Charset::class); | ||
} | ||
|
||
if (null === $value) { | ||
return; | ||
} | ||
|
||
if (!\is_string($value)) { | ||
throw new UnexpectedValueException($value, 'string'); | ||
} | ||
|
||
if (!\in_array($detected = mb_detect_encoding($value, $constraint->encodings, true), $constraint->encodings, true)) { | ||
$this->context->buildViolation($constraint->message) | ||
->setParameter('{{ detected }}', $detected) | ||
->setParameter('{{ encodings }}', implode('", "', $constraint->encodings)) | ||
->setCode(Charset::BAD_ENCODING_ERROR) | ||
->addViolation(); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
src/Symfony/Component/Validator/Tests/Constraints/CharsetTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Validator\Constraints\Charset; | ||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | ||
use Symfony\Component\Validator\Mapping\ClassMetadata; | ||
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; | ||
|
||
class CharsetTest extends TestCase | ||
{ | ||
public function testSingleEncodingCanBeSet() | ||
{ | ||
$encoding = new Charset('UTF-8'); | ||
|
||
$this->assertSame(['UTF-8'], $encoding->encodings); | ||
} | ||
|
||
public function testMultipleEncodingCanBeSet() | ||
{ | ||
$encoding = new Charset(['ASCII', 'UTF-8']); | ||
|
||
$this->assertSame(['ASCII', 'UTF-8'], $encoding->encodings); | ||
} | ||
|
||
public function testThrowsOnNoCharset() | ||
{ | ||
$this->expectException(ConstraintDefinitionException::class); | ||
$this->expectExceptionMessage('The "Symfony\Component\Validator\Constraints\Charset" constraint requires at least one encoding.'); | ||
|
||
new Charset(); | ||
} | ||
|
||
public function testAttributes() | ||
{ | ||
$metadata = new ClassMetadata(CharsetDummy::class); | ||
$loader = new AttributeLoader(); | ||
$this->assertTrue($loader->loadClassMetadata($metadata)); | ||
|
||
[$aConstraint] = $metadata->properties['a']->getConstraints(); | ||
$this->assertSame(['UTF-8'], $aConstraint->encodings); | ||
|
||
[$bConstraint] = $metadata->properties['b']->getConstraints(); | ||
$this->assertSame(['ASCII', 'UTF-8'], $bConstraint->encodings); | ||
} | ||
} | ||
|
||
class CharsetDummy | ||
{ | ||
#[Charset('UTF-8')] | ||
private string $a; | ||
|
||
#[Charset(['ASCII', 'UTF-8'])] | ||
private string $b; | ||
} |
86 changes: 86 additions & 0 deletions
86
src/Symfony/Component/Validator/Tests/Constraints/CharsetValidatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraints\Charset; | ||
use Symfony\Component\Validator\Constraints\CharsetValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; | ||
|
||
class CharsetValidatorTest extends ConstraintValidatorTestCase | ||
{ | ||
protected function createValidator(): CharsetValidator | ||
{ | ||
return new CharsetValidator(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideValidValues | ||
*/ | ||
public function testEncodingIsValid(string $value, array $encodings) | ||
{ | ||
$this->validator->validate($value, new Charset(encodings: $encodings)); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidValues | ||
*/ | ||
public function testInvalidValues(string $value, array $encodings) | ||
{ | ||
$this->validator->validate($value, new Charset(encodings: $encodings)); | ||
|
||
$this->buildViolation('The detected encoding "{{ detected }}" does not match one of the accepted encoding: "{{ encodings }}".') | ||
->setParameter('{{ detected }}', mb_detect_encoding($value, $encodings, true)) | ||
->setParameter('{{ encodings }}', implode(', ', $encodings)) | ||
->setCode(Charset::BAD_ENCODING_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidTypes | ||
*/ | ||
public function testNonStringValues(mixed $value) | ||
{ | ||
$this->expectException(UnexpectedValueException::class); | ||
$this->expectExceptionMessageMatches('/Expected argument of type "string", ".*" given/'); | ||
|
||
$this->validator->validate($value, new Charset(encodings: ['UTF-8'])); | ||
} | ||
|
||
public static function provideValidValues() | ||
{ | ||
yield ['my ascii string', ['ASCII']]; | ||
yield ['my ascii string', ['UTF-8']]; | ||
yield ['my ascii string', ['ASCII', 'UTF-8']]; | ||
yield ['my ûtf 8', ['ASCII', 'UTF-8']]; | ||
yield ['my ûtf 8', ['UTF-8']]; | ||
yield ['ώ', ['UTF-16']]; | ||
} | ||
|
||
public static function provideInvalidValues() | ||
{ | ||
yield ['my non-Äscîi string', ['ASCII']]; | ||
yield ['😊', ['7bit']]; | ||
} | ||
|
||
public static function provideInvalidTypes() | ||
{ | ||
yield [true]; | ||
yield [false]; | ||
yield [1]; | ||
yield [1.1]; | ||
yield [[]]; | ||
yield [new \stdClass()]; | ||
} | ||
} |