From 8e1ffc8b99801c47ac3afd24d3bd5c8fef0e08e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Masforn=C3=A9?= Date: Mon, 6 Apr 2020 21:52:50 +0200 Subject: [PATCH] Feature #36362 add Isin validator constraint Feature #36362 typo Fix PR feedbacks Fix coding standard ticket 36362 fix PR feedbacks Update src/Symfony/Component/Validator/Constraints/IsinValidator.php Co-Authored-By: Yannis Foucher <33806646+YaFou@users.noreply.github.com> --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Component/Validator/Constraints/Isin.php | 38 +++++ .../Validator/Constraints/IsinValidator.php | 92 ++++++++++++ .../Resources/translations/validators.en.xlf | 4 + .../Resources/translations/validators.fr.xlf | 4 + .../Tests/Constraints/IsinValidatorTest.php | 135 ++++++++++++++++++ 6 files changed, 274 insertions(+) create mode 100644 src/Symfony/Component/Validator/Constraints/Isin.php create mode 100644 src/Symfony/Component/Validator/Constraints/IsinValidator.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index df48d15b3ffe4..fb653bd9db939 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -28,6 +28,7 @@ CHANGELOG * }) */ ``` + * added the `Isin` constraint and validator 5.1.0 ----- diff --git a/src/Symfony/Component/Validator/Constraints/Isin.php b/src/Symfony/Component/Validator/Constraints/Isin.php new file mode 100644 index 0000000000000..586ea829d2d5f --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Isin.php @@ -0,0 +1,38 @@ + + * + * 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; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Laurent Masforné + */ +class Isin extends Constraint +{ + const VALIDATION_LENGTH = 12; + const VALIDATION_PATTERN = '/[A-Z]{2}[A-Z0-9]{9}[0-9]{1}/'; + + const INVALID_LENGTH_ERROR = '88738dfc-9ed5-ba1e-aebe-402a2a9bf58e'; + const INVALID_PATTERN_ERROR = '3d08ce0-ded9-a93d-9216-17ac21265b65e'; + const INVALID_CHECKSUM_ERROR = '32089b-0ee1-93ba-399e-aa232e62f2d29d'; + + protected static $errorNames = [ + self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR', + self::INVALID_PATTERN_ERROR => 'INVALID_PATTERN_ERROR', + self::INVALID_CHECKSUM_ERROR => 'INVALID_CHECKSUM_ERROR', + ]; + + public $message = 'This is not a valid International Securities Identification Number (ISIN).'; +} diff --git a/src/Symfony/Component/Validator/Constraints/IsinValidator.php b/src/Symfony/Component/Validator/Constraints/IsinValidator.php new file mode 100644 index 0000000000000..9ae31acb14669 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/IsinValidator.php @@ -0,0 +1,92 @@ + + * + * 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; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * @author Laurent Masforné + * + * @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number + */ +class IsinValidator extends ConstraintValidator +{ + /** + * @var ValidatorInterface + */ + private $validator; + + public function __construct(ValidatorInterface $validator) + { + $this->validator = $validator; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Isin) { + throw new UnexpectedTypeException($constraint, Isin::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = strtoupper($value); + + if (Isin::VALIDATION_LENGTH !== \strlen($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_LENGTH_ERROR) + ->addViolation(); + + return; + } + + if (!preg_match(Isin::VALIDATION_PATTERN, $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_PATTERN_ERROR) + ->addViolation(); + + return; + } + + if (!$this->isCorrectChecksum($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_CHECKSUM_ERROR) + ->addViolation(); + } + } + + private function isCorrectChecksum(string $input): bool + { + $characters = str_split($input); + foreach ($characters as $i => $char) { + $characters[$i] = \intval($char, 36); + } + $number = implode('', $characters); + + return 0 === $this->validator->validate($number, new Luhn())->count(); + } +} diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 674ccf5c30ea6..ecc73e48aa1ef 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -382,6 +382,10 @@ Each element of this collection should satisfy its own set of constraints. Each element of this collection should satisfy its own set of constraints. + + This value is not a valid International Securities Identification Number (ISIN). + This value is not a valid International Securities Identification Number (ISIN). + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index c44ade69e0713..a4dd54295b46a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -382,6 +382,10 @@ Each element of this collection should satisfy its own set of constraints. Chaque élément de cette collection doit satisfaire à son propre jeu de contraintes. + + This value is not a valid International Securities Identification Number (ISIN). + Cette valeur n'est pas un code international de sécurité valide (ISIN). + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php new file mode 100644 index 0000000000000..0822fb5ad65f9 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php @@ -0,0 +1,135 @@ +getValidator()); + } + + public function testNullIsValid() + { + $this->validator->validate(null, new Isin()); + + $this->assertNoViolation(); + } + + public function testEmptyStringIsValid() + { + $this->validator->validate('', new Isin()); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getValidIsin + */ + public function testValidIsin($isin) + { + $this->validator->validate($isin, new Isin()); + $this->assertNoViolation(); + } + + public function getValidIsin() + { + return [ + ['XS2125535901'], // Goldman Sachs International + ['DE000HZ8VA77'], // UniCredit Bank AG + ['CH0528261156'], // Leonteq Securities AG [Guernsey] + ['US0378331005'], // Apple, Inc. + ['AU0000XVGZA3'], // TREASURY CORP VICTORIA 5 3/4% 2005-2016 + ['GB0002634946'], // BAE Systems + ['CH0528261099'], // Leonteq Securities AG [Guernsey] + ['XS2155672814'], // OP Corporate Bank plc + ['XS2155687259'], // Orbian Financial Services III, LLC + ['XS2155696672'], // Sheffield Receivables Company LLC + ]; + } + + /** + * @dataProvider getIsinWithInvalidLenghFormat + */ + public function testIsinWithInvalidFormat($isin) + { + $this->assertViolationRaised($isin, Isin::INVALID_LENGTH_ERROR); + } + + public function getIsinWithInvalidLenghFormat() + { + return [ + ['X'], + ['XS'], + ['XS2'], + ['XS21'], + ['XS215'], + ['XS2155'], + ['XS21556'], + ['XS215569'], + ['XS2155696'], + ['XS21556966'], + ['XS215569667'], + ]; + } + + /** + * @dataProvider getIsinWithInvalidPattern + */ + public function testIsinWithInvalidPattern($isin) + { + $this->assertViolationRaised($isin, Isin::INVALID_PATTERN_ERROR); + } + + public function getIsinWithInvalidPattern() + { + return [ + ['X12155696679'], + ['123456789101'], + ['XS215569667E'], + ['XS215E69667A'], + ]; + } + + /** + * @dataProvider getIsinWithValidFormatButIncorrectChecksum + */ + public function testIsinWithValidFormatButIncorrectChecksum($isin) + { + $this->assertViolationRaised($isin, Isin::INVALID_CHECKSUM_ERROR); + } + + public function getIsinWithValidFormatButIncorrectChecksum() + { + return [ + ['XS2112212144'], + ['DE013228VA77'], + ['CH0512361156'], + ['XS2125660123'], + ['XS2012587408'], + ['XS2012380102'], + ['XS2012239364'], + ]; + } + + private function assertViolationRaised($isin, $code) + { + $constraint = new Isin([ + 'message' => 'myMessage', + ]); + + $this->validator->validate($isin, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$isin.'"') + ->setCode($code) + ->assertRaised(); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy