├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
└── NifValidator.php
└── tests
└── NifValidatorTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | .idea/
3 | composer.lock
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 7.0
4 | - 7.1
5 | - 7.2
6 | - 7.3
7 | - 7.4
8 | - 8.0
9 | - 8.1
10 |
11 | before_script: composer install
12 | script: vendor/bin/phpunit
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2017 Ulabox SL
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nif-validator
2 |
3 | [](http://travis-ci.org/ulabox/nif-validator)
4 |
5 | A modern PHP 7.0+ Spanish NIF (Número de Indentifación Fiscal) validator.
6 |
7 | ## Why another NIF validator?
8 |
9 | Other NIF validators we saw either had really obscure code or just implemented a validator for the DNI/NIE, not the CIF.
10 |
11 | ## Installation
12 |
13 | Using Composer:
14 |
15 | ```
16 | composer require ulabox/nif-validator
17 | ```
18 |
19 | ## Usage
20 |
21 |
22 | ```php
23 | =7.0"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": "~6.2"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "NifValidator\\": "src/"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "NifValidator\\": "tests/"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | ./tests/
12 |
13 |
14 |
15 |
16 |
17 | ./tests/
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/NifValidator.php:
--------------------------------------------------------------------------------
1 | [0-9]{8})(?[A-Z])$#';
16 | const NIE_REGEX = '#^(?['. self::NIE_TYPES .'])(?[0-9]{7})(?[A-Z])$#';
17 | const OTHER_PERSONAL_NIF_REGEX = '#^(?[KLM])(?[0-9]{7})(?[0-9A-Z])$#';
18 | const CIF_REGEX = '#^(?[ABCDEFGHJNPQRSUVW])(?[0-9]{7})(?[0-9A-Z])$#';
19 |
20 | /**
21 | * Validate Spanish NIFS
22 | * Input is not uppercased, or stripped of any incorrect characters
23 | */
24 | public static function isValid(string $nif): bool
25 | {
26 | return self::isValidDni($nif) || self::isValidNie($nif) || self::isValidCif($nif) || self::isValidOtherPersonalNif($nif);
27 | }
28 |
29 | /**
30 | * Validate Spanish NIFS given to persons
31 | */
32 | public static function isValidPersonal(string $nif): bool
33 | {
34 | return self::isValidDni($nif) || self::isValidNie($nif) || self::isValidOtherPersonalNif($nif);
35 | }
36 |
37 | /**
38 | * Validate Spanish NIFS given to non-personal entities (e.g. companies, public corporations, ngos...)
39 | */
40 | public static function isValidEntity(string $nif): bool
41 | {
42 | return self::isValidCif($nif);
43 | }
44 |
45 | /**
46 | * DNI validation is pretty straight forward.
47 | * Just mod 23 the 8 digit number and compare it to the check table
48 | */
49 | public static function isValidDni(string $dni): bool
50 | {
51 | if (!preg_match(self::DNI_REGEX, $dni, $matches)) {
52 | return false;
53 | }
54 | if ('00000000' === $matches['number']) {
55 | return false;
56 | }
57 |
58 | return self::DNINIE_CHECK_TABLE[$matches['number'] % 23] === $matches['check'];
59 | }
60 |
61 | /**
62 | * NIE validation is similar to the DNI.
63 | * The first letter needs an equivalent number before the mod operation
64 | */
65 | public static function isValidNie(string $nie): bool
66 | {
67 | if (!preg_match(self::NIE_REGEX, $nie, $matches)) {
68 | return false;
69 | }
70 |
71 | $nieType = strpos(self::NIE_TYPES, $matches['type']);
72 | $nie = $nieType . $matches['number'];
73 |
74 | return self::DNINIE_CHECK_TABLE[$nie % 23] === $matches['check'];
75 | }
76 |
77 | /**
78 | * Other personal NIFS are meant for temporary residents that do not qualify for a NIE but nonetheless need a NIF
79 | *
80 | * See references
81 | *
82 | * @see https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
83 | * @see https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
84 | */
85 | public static function isValidOtherPersonalNif(string $nif): bool
86 | {
87 | if (!preg_match(self::OTHER_PERSONAL_NIF_REGEX, $nif, $matches)) {
88 | return false;
89 | }
90 |
91 | return self::isValidNifCheck($nif, $matches);
92 | }
93 |
94 | /**
95 | * CIFS are only meant for non-personal entities
96 | *
97 | * See references
98 | *
99 | * @see https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
100 | * @see https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
101 | */
102 | public static function isValidCif(string $cif): bool
103 | {
104 | if (!preg_match(self::CIF_REGEX, $cif, $matches)) {
105 | return false;
106 | }
107 |
108 | return self::isValidNifCheck($cif, $matches);
109 | }
110 |
111 | private static function isValidNifCheck(string $nif, array $matches): bool
112 | {
113 | $split = str_split($matches['number']);
114 |
115 | $even = array_filter($split, function($key) {
116 | return $key & 1;
117 | }, ARRAY_FILTER_USE_KEY);
118 | $sumEven = array_sum($even);
119 |
120 | $odd = array_filter($split, function($key) {
121 | return !($key & 1);
122 | }, ARRAY_FILTER_USE_KEY);
123 | $sumOdd = array_reduce($odd, function($carry, $item) {
124 | return $carry + array_sum(str_split($item * 2));
125 | });
126 |
127 | $calculatedCheckDigit = (10 - ($sumEven + $sumOdd) % 10) % 10;
128 |
129 | //Nifs with only letters
130 | if (self::nifHasLetterCheck($matches['type'], $nif)) {
131 | return self::checkNifLetter($calculatedCheckDigit, $matches['check']);
132 | }
133 |
134 | //Nifs with only numbers
135 | if (self::nifHasNumberCheck($matches['type'], $nif)) {
136 | return self::checkNifNumber($calculatedCheckDigit, $matches['check']);
137 | }
138 |
139 | //Nifs that accept both
140 | return
141 | self::checkNifLetter($calculatedCheckDigit, $matches['check'])
142 | || self::checkNifNumber($calculatedCheckDigit, $matches['check'])
143 | ;
144 | }
145 |
146 | private static function nifHasLetterCheck(string $nifType, string $nif): bool
147 | {
148 | return
149 | false !== strpos(self::NIF_TYPES_WITH_LETTER_CHECK, $nifType)
150 | || ('0' === $nif[0] && '0' === $nif[1])
151 | ;
152 | }
153 |
154 | private static function checkNifLetter(int $calculatedCheckDigit, string $checkDigit): bool
155 | {
156 | return self::NIF_LETTER_CHECK_TABLE[$calculatedCheckDigit] === $checkDigit;
157 | }
158 |
159 | private static function nifHasNumberCheck(string $nifType, string $nif): bool
160 | {
161 | return false !== strpos(self::NIF_TYPES_WITH_NUMBER_CHECK, $nifType);
162 | }
163 |
164 | private static function checkNifNumber(string $calculatedCheckDigit, string $checkDigit): bool
165 | {
166 | return $calculatedCheckDigit === $checkDigit;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/tests/NifValidatorTest.php:
--------------------------------------------------------------------------------
1 | validDnis(), $this->validNies(), $this->validOtherNifs());
199 | }
200 |
201 | public function invalidPersonalNifs()
202 | {
203 | return array_merge($this->invalidDnis(), $this->invalidNies(), $this->invalidOtherNifs());
204 | }
205 |
206 | public function validEntityNifs()
207 | {
208 | return [
209 | //CIF
210 | ['A58818501'],
211 | ['B65410011'],
212 | ['V7565938C'],
213 | ['V75659383'],
214 | ['F0605378I'],
215 | ['Q2238877A'],
216 | ['D40022956'],
217 | ];
218 | }
219 |
220 | public function invalidEntityNifs()
221 | {
222 | return [
223 | //CIF
224 | ['A5881850B'],
225 | ['B65410010'],
226 | ['V75659382'],
227 | ['V7565938B'],
228 | ['F06053787'],
229 | ['Q22388770'],
230 | ['D4002295J'],
231 | ];
232 | }
233 | }
234 |
--------------------------------------------------------------------------------