Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.04% covered (success)
98.04%
50 / 51
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Iban
98.04% covered (success)
98.04%
50 / 51
75.00% covered (warning)
75.00%
3 / 4
19
0.00% covered (danger)
0.00%
0 / 1
 isValid
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
8
 validateZeros
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 validateNumeric
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 validateChecksum
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Validation\Finance
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Validation\Finance;
16
17use phpOMS\Validation\ValidatorAbstract;
18
19/**
20 * Iban validation.
21 *
22 * @package phpOMS\Validation\Finance
23 * @license OMS License 2.0
24 * @link    https://jingga.app
25 * @since   1.0.0
26 */
27final class Iban extends ValidatorAbstract
28{
29    /**
30     * {@inheritdoc}
31     */
32    public static function isValid(mixed $value, array $constraints = null) : bool
33    {
34        if (!\is_string($value)) {
35            return false;
36        }
37
38        $value = \str_replace(' ', '', \strtolower($value));
39
40        $temp = \substr($value, 0, 2);
41        if ($temp === false) {
42            return false; // @codeCoverageIgnore
43        }
44
45        $enumName = 'C_' . \strtoupper($temp);
46
47        if (!IbanEnum::isValidName($enumName)) {
48            self::$error = IbanErrorType::INVALID_COUNTRY;
49
50            return false;
51        }
52
53        /** @var string $enumVal */
54        $enumVal = IbanEnum::getByName($enumName);
55        $layout  = \str_replace(' ', '', $enumVal);
56
57        if (\strlen($value) !== \strlen($layout)) {
58            self::$error = IbanErrorType::INVALID_LENGTH;
59
60            return false;
61        }
62
63        if (!self::validateZeros($value, $layout)) {
64            self::$error = IbanErrorType::EXPECTED_ZERO;
65
66            return false;
67        }
68
69        if (!self::validateNumeric($value, $layout)) {
70            self::$error = IbanErrorType::EXPECTED_NUMERIC;
71
72            return false;
73        }
74
75        if (!self::validateChecksum($value)) {
76            self::$error = IbanErrorType::INVALID_CHECKSUM;
77
78            return false;
79        }
80
81        return true;
82    }
83
84    /**
85     * Validate positions that should have zeros
86     *
87     * @param string $iban   Iban to validate
88     * @param string $layout Iban layout
89     *
90     * @return bool
91     *
92     * @since 1.0.0
93     */
94    private static function validateZeros(string $iban, string $layout) : bool
95    {
96        if (\strpos($layout, '0') === false) {
97            return true;
98        }
99
100        $lastPos = 0;
101        while (($lastPos = \strpos($layout, '0', $lastPos)) !== false) {
102            if ($iban[$lastPos] !== '0') {
103                return false;
104            }
105
106            ++$lastPos;
107        }
108
109        return true;
110    }
111
112    /**
113     * Validate positions that should be numeric
114     *
115     * @param string $iban   Iban to validate
116     * @param string $layout Iban layout
117     *
118     * @return bool
119     *
120     * @since 1.0.0
121     */
122    private static function validateNumeric(string $iban, string $layout) : bool
123    {
124        if (\strpos($layout, 'n') === false) {
125            return true;
126        }
127
128        $lastPos = 0;
129        while (($lastPos = \strpos($layout, 'n', $lastPos)) !== false) {
130            if (!\is_numeric($iban[$lastPos])) {
131                return false;
132            }
133
134            ++$lastPos;
135        }
136
137        return true;
138    }
139
140    /**
141     * Validate checksum
142     *
143     * @param string $iban Iban to validate
144     *
145     * @return bool
146     *
147     * @since 1.0.0
148     */
149    private static function validateChecksum(string $iban) : bool
150    {
151        $chars      = ['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17, 'i' => 18,
152                       'j' => 19, 'k' => 20, 'l' => 21, 'm' => 22, 'n' => 23, 'o' => 24, 'p' => 25, 'q' => 26, 'r' => 27,
153                       's' => 28, 't' => 29, 'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35,];
154        $moved      = \substr($iban, 4) . \substr($iban, 0, 4);
155        $movedArray = (array) \str_split($moved);
156        $new        = '';
157
158        foreach ($movedArray as $key => $value) {
159            if (!\is_numeric($movedArray[$key])) {
160                $movedArray[$key] = $chars[$movedArray[$key]];
161            }
162
163            $new .= $movedArray[$key];
164        }
165
166        return \bcmod($new, '97') == 1;
167    }
168}