├── .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 | [![Build Status](https://api.travis-ci.org/ulabox/nif-validator.png?branch=master)](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 | --------------------------------------------------------------------------------