Skip to content

Commit 059a30a

Browse files
lmasforneYaFou
authored andcommitted
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>
1 parent ef19a03 commit 059a30a

File tree

6 files changed

+397
-0
lines changed

6 files changed

+397
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ CHANGELOG
2828
* })
2929
*/
3030
```
31+
* added `Isin` constraints
3132

3233
5.1.0
3334
-----
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
16+
/**
17+
* @Annotation
18+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
19+
*
20+
* @author Laurent Masforné <l.masforne@gmail.com>
21+
*/
22+
class Isin extends Constraint
23+
{
24+
const VALIDATION_LENGTH = 12;
25+
const VALIDATION_PATTERN = '/[A-Z]{2}[A-Z0-9]{9}[0-9]{1}/';
26+
27+
const INVALID_LENGTH_ERROR = '88738dfc-9ed5-ba1e-aebe-402a2a9bf58e';
28+
const INVALID_PATTERN_ERROR = '3d08ce0-ded9-a93d-9216-17ac21265b65e';
29+
const INVALID_CHECKSUM_ERROR = '32089b-0ee1-93ba-399e-aa232e62f2d29d';
30+
31+
protected static $errorNames = [
32+
self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR',
33+
self::INVALID_PATTERN_ERROR => 'INVALID_PATTERN_ERROR',
34+
self::INVALID_CHECKSUM_ERROR => 'INVALID_CHECKSUM_ERROR',
35+
];
36+
37+
public $message = 'This is not a valid International Securities Identification Number (ISIN).';
38+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 Laurent Masforné <l.masforne@gmail.com>
21+
*
22+
* @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number
23+
*/
24+
class IsinValidator extends ConstraintValidator
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function validate($value, Constraint $constraint)
30+
{
31+
if (!$constraint instanceof Isin) {
32+
throw new UnexpectedTypeException($constraint, Isin::class);
33+
}
34+
35+
if (null === $value || '' === $value) {
36+
return;
37+
}
38+
39+
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
40+
throw new UnexpectedValueException($value, 'string');
41+
}
42+
43+
$value = strtoupper($value);
44+
45+
if (Isin::VALIDATION_LENGTH !== \strlen($value)) {
46+
$this->context->buildViolation($constraint->message)
47+
->setParameter('{{ value }}', $this->formatValue($value))
48+
->setCode(Isin::INVALID_LENGTH_ERROR)
49+
->addViolation();
50+
51+
return;
52+
}
53+
54+
if (!preg_match(Isin::VALIDATION_PATTERN, $value)) {
55+
$this->context->buildViolation($constraint->message)
56+
->setParameter('{{ value }}', $this->formatValue($value))
57+
->setCode(Isin::INVALID_PATTERN_ERROR)
58+
->addViolation();
59+
60+
return;
61+
}
62+
63+
if (!$this->isCorrectChecksum($value)) {
64+
$this->context->buildViolation($constraint->message)
65+
->setParameter('{{ value }}', $this->formatValue($value))
66+
->setCode(Isin::INVALID_CHECKSUM_ERROR)
67+
->addViolation();
68+
69+
return;
70+
}
71+
72+
return $value;
73+
}
74+
75+
private function isCorrectChecksum($input)
76+
{
77+
$characters = str_split($input);
78+
foreach ($characters as $i => $char) {
79+
$characters[$i] = \intval($char, 36);
80+
}
81+
$checkDigit = array_pop($characters);
82+
$number = implode('', $characters);
83+
$expectedCheckDigit = $this->getCheckDigit($number);
84+
85+
return $checkDigit === $expectedCheckDigit;
86+
}
87+
88+
/**
89+
* This method performs the luhn algorithm
90+
* to obtain a check digit.
91+
*/
92+
private function getCheckDigit($input)
93+
{
94+
// first split up the string
95+
$numbers = str_split($input);
96+
97+
// calculate the positional value.
98+
// when there is an even number of digits the second group will be multiplied, so p starts on 0
99+
// when there is an odd number of digits the first group will be multiplied, so p starts on 1
100+
$p = \count($numbers) % 2;
101+
// run through each number
102+
foreach ($numbers as $i => $num) {
103+
$num = (int) $num;
104+
// every positional number needs to be multiplied by 2
105+
if ($p % 2) {
106+
$num = $num * 2;
107+
// if the result was more than 9
108+
// add the individual digits
109+
$num = array_sum(str_split($num));
110+
}
111+
$numbers[$i] = $num;
112+
++$p;
113+
}
114+
115+
// get the total value of all the digits
116+
$sum = array_sum($numbers);
117+
118+
// get the remainder when dividing by 10
119+
$mod = $sum % 10;
120+
121+
// subtract from 10
122+
$rem = 10 - $mod;
123+
124+
// mod from 10 to catch if the result was 0
125+
$digit = $rem % 10;
126+
127+
return $digit;
128+
}
129+
}

src/Symfony/Component/Validator/Resources/translations/validators.en.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@
382382
<source>Each element of this collection should satisfy its own set of constraints.</source>
383383
<target>Each element of this collection should satisfy its own set of constraints.</target>
384384
</trans-unit>
385+
<trans-unit id="99">
386+
<source>This value is not a valid International Securities Identification Number (ISIN).</source>
387+
<target>This value is not a valid International Securities Identification Number (ISIN).</target>
388+
</trans-unit>
385389
</body>
386390
</file>
387391
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@
382382
<source>Each element of this collection should satisfy its own set of constraints.</source>
383383
<target>Chaque élément de cette collection doit satisfaire à son propre jeu de contraintes.</target>
384384
</trans-unit>
385+
<trans-unit id="99">
386+
<source>This value is not a valid International Securities Identification Number (ISIN).</source>
387+
<target>Ce n'est pas un code international de sécurité valide (ISIN).</target>
388+
</trans-unit>
385389
</body>
386390
</file>
387391
</xliff>

0 commit comments

Comments
 (0)