Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
85.71% covered (warning)
85.71%
6 / 7
CRAP
92.86% covered (success)
92.86%
26 / 28
InnValidator
0.00% covered (danger)
0.00%
0 / 1
85.71% covered (warning)
85.71%
6 / 7
19.13
92.86% covered (success)
92.86%
26 / 28
 getDefaultErrorMessages
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 checkValueLength
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 validateValue
0.00% covered (danger)
0.00%
0 / 1
7.39
80.00% covered (warning)
80.00%
8 / 10
 checkN1CheckNumber
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 checkN2CheckNumber
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 calculateCheckSum
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 calculateCheckNumber
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
1<?php
2
3namespace shumorkiniv\validators;
4
5
6/**
7 * Class InnValidator
8 * InnValidator validates that the attribute value is valid INN.
9 *
10 * Note, this validator should only be used with string-typed or integer-typed attributes.
11 *
12 * @author Shumorkin Ilya <shumorkinilya@mail.ru>
13 */
14class InnValidator extends RequisitesValidator
15{
16    /** @var int Divider for check sum */
17    const DIVIDER = 11;
18    /** @var int Index of number wich compare with control */
19    const COMPARED_NUMBER_LEGAL_INDEX = 9;
20    /** @var int Index of number wich compare with first control */
21    const COMPARED_NUMBER_INDIVIDUAL_INDEX1 = 10;
22    /** @var int Index of number wich compare with second control */
23    const COMPARED_NUMBER_INDIVIDUAL_INDEX2 = 11;
24    /** @var int Length of INN for legal entities */
25    const LEGAL_ENTITY_LENGTH = 10;
26    /** @var int Length of INN for individuals */
27    const INDIVIDUAL_LENGTH = 12;
28
29    /** @var int[] Coefficients for check first check number of 12-digit INN */
30    private const N1_COEFFICIENTS_INDIVIDUAL = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8];
31    /** @var int[] Coefficients for check first check number of 12-digit INN */
32    private const N2_COEFFICIENTS_INDIVIDUAL = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8];
33    /** @var int[] Coefficients for check check number of 10-digit INN */
34    private const N1_COEFFICIENTS_LEGAL_ENTITY = [2, 4, 10, 3, 5, 9, 4, 6, 8];
35
36    /**
37     * @inheritdoc
38     */
39    protected function getDefaultErrorMessages(): array
40    {
41        return [
42            'wrongLength' => 'ИНН должен состоять из 11 или 12 чисел.',
43            'wrongChar' => 'ИНН должен состоять только из цифр.',
44            'invalidValue' => 'Несуществующий ИНН.',
45        ];
46    }
47
48    /**
49     * @inheritdoc
50     */
51    protected function checkValueLength(string $value): bool
52    {
53        return strlen($value) === self::LEGAL_ENTITY_LENGTH || strlen($value) === self::INDIVIDUAL_LENGTH;
54    }
55
56    /**
57     * @inheritdoc
58     */
59    protected function validateValue($value): ?array
60    {
61        $preValidate = $this->preValidate($value);
62
63        if (!empty($preValidate)) {
64            return $preValidate;
65        }
66
67        if ($this->length === self::LEGAL_ENTITY_LENGTH) {
68            if (!$this->checkN1CheckNumber()) {
69                return [$this->message, []];
70            }
71        }
72
73        if ($this->length === self::INDIVIDUAL_LENGTH) {
74            if (!$this->checkN1CheckNumber() || !$this->checkN2CheckNumber()) {
75                return [$this->message, []];
76            }
77        }
78
79        return null;
80    }
81
82    /**
83     * Check of n1 - first check number
84     *
85     * @return bool
86     */
87    private function checkN1CheckNumber(): bool
88    {
89        $isLegal = $this->length === self::LEGAL_ENTITY_LENGTH;
90
91        $coefficients = $isLegal ? self::N1_COEFFICIENTS_LEGAL_ENTITY : self::N1_COEFFICIENTS_INDIVIDUAL;
92        $checkNumber = $this->calculateCheckNumber($this->calculateCheckSum($coefficients));
93
94        if ($isLegal) {
95            return $checkNumber === $this->numbers[self::COMPARED_NUMBER_LEGAL_INDEX];
96        }
97
98        return $checkNumber === $this->numbers[self::COMPARED_NUMBER_INDIVIDUAL_INDEX1];
99    }
100
101    /**
102     * Check of n2 - seond check number (only for individual)
103     *
104     * @return bool
105     */
106    private function checkN2CheckNumber(): bool
107    {
108        $checkNumber = $this->calculateCheckNumber($this->calculateCheckSum(self::N2_COEFFICIENTS_INDIVIDUAL));
109
110        return $checkNumber === $this->numbers[self::COMPARED_NUMBER_INDIVIDUAL_INDEX2];
111    }
112
113    /**
114     * Calculating of check sum
115     *
116     * @param int[] $coefficients
117     * @return int
118     */
119    private function calculateCheckSum(array $coefficients): int
120    {
121        $checkSum = 0;
122
123        foreach ($this->numbers as $index => $number) {
124            if (isset($coefficients[$index])) {
125                $checkSum += $number * $coefficients[$index];
126            }
127        }
128
129        return $checkSum;
130    }
131
132    /**
133     * Calculating of check number
134     *
135     * @param int $checkSum
136     * @return int
137     */
138    private function calculateCheckNumber(int $checkSum): int
139    {
140        $diff = $checkSum / self::DIVIDER;
141
142        $checkNumber = $checkSum - self::DIVIDER * (int)$diff;
143
144        return $checkNumber > 9 ? $checkNumber % 10 : $checkNumber;
145    }
146}