Create "Hetu" rule

This rule validates Finnish personal identity codes.

Co-authored-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Ville Hukkamäki 2024-03-12 22:36:45 +02:00 committed by Henrique Moody
parent 52b75bc1ed
commit a5d042be7b
No known key found for this signature in database
GPG key ID: 221E9281655813A6
13 changed files with 235 additions and 0 deletions

View file

@ -108,6 +108,7 @@
- [Cnh](rules/Cnh.md)
- [Cnpj](rules/Cnpj.md)
- [Cpf](rules/Cpf.md)
- [Hetu](rules/Hetu.md)
- [Imei](rules/Imei.md)
- [Isbn](rules/Isbn.md)
- [Luhn](rules/Luhn.md)
@ -344,6 +345,7 @@
- [Graph](rules/Graph.md)
- [GreaterThan](rules/GreaterThan.md)
- [GreaterThanOrEqual](rules/GreaterThanOrEqual.md)
- [Hetu](rules/Hetu.md)
- [HexRgbColor](rules/HexRgbColor.md)
- [Iban](rules/Iban.md)
- [Identical](rules/Identical.md)

View file

@ -24,6 +24,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)

View file

@ -21,6 +21,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnh](Cnh.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)

View file

@ -37,6 +37,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)

37
docs/rules/Hetu.md Normal file
View file

@ -0,0 +1,37 @@
# Hetu
- `Hetu()`
Validates a Finnish personal identity code ([HETU][]).
```php
v::hetu()->validate('010106A9012'); // true
v::hetu()->validate('290199-907A'); // true
v::hetu()->validate('280291+923X'); // true
v::hetu()->validate('010106_9012'); // false
```
The validation is case-sensitive.
## Categorization
- Identifications
## Changelog
| Version | Description |
|--------:|-------------|
| 3.0.0 | Created |
***
See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Imei](Imei.md)
- [Nif](Nif.md)
- [PortugueseNif](PortugueseNif.md)
[HETU]: https://en.wikipedia.org/wiki/National_identification_number#Finland

View file

@ -26,6 +26,7 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Isbn](Isbn.md)
- [Luhn](Luhn.md)

View file

@ -26,4 +26,5 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [PortugueseNif](PortugueseNif.md)

View file

@ -26,4 +26,5 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Nif](Nif.md)

View file

@ -138,6 +138,8 @@ interface ChainedValidator extends Validatable
public function greaterThan(mixed $compareTo): ChainedValidator;
public function hetu(): ChainedValidator;
public function hexRgbColor(): ChainedValidator;
public function iban(): ChainedValidator;

View file

@ -140,6 +140,8 @@ interface StaticValidator
public static function greaterThan(mixed $compareTo): ChainedValidator;
public static function hetu(): ChainedValidator;
public static function hexRgbColor(): ChainedValidator;
public static function iban(): ChainedValidator;

58
library/Rules/Hetu.php Normal file
View file

@ -0,0 +1,58 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
use function preg_match;
use function str_split;
/**
* @see https://en.wikipedia.org/wiki/National_identification_number#Finland
*/
#[Template(
'{{name}} must be a valid Finnish personal identity code',
'{{name}} must not be a valid Finnish personal identity code',
)]
final class Hetu extends Simple
{
use CanValidateDateTime;
public function validate(mixed $input): bool
{
if (!is_string($input)) {
return false;
}
if (!preg_match('/^(\d{2})(\d{2})(\d{2})([+\-A-FU-Y])(\d{3})([0-9A-FHJ-NPR-Y])$/', $input, $matches)) {
return false;
}
[, $day, $month, $year, $centuryMarker, $individualNumber, $controlCharacter] = $matches;
// @phpstan-ignore-next-line
$century = match ($centuryMarker) {
'+' => '18',
'-', 'U', 'V', 'W', 'X', 'Y' => '19',
'A', 'B', 'C', 'D', 'E', 'F' => '20',
};
if (!$this->isDateTime('dmY', $day . $month . $century . $year)) {
return false;
}
$id = $day . $month . $year . $individualNumber;
$validationKeys = str_split('0123456789ABCDEFHJKLMNPRSTUVWXY');
return $validationKeys[$id % 31] === $controlCharacter;
}
}

View file

@ -0,0 +1,49 @@
--FILE--
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Respect\Validation\Validator as v;
run([
'Default' => [v::hetu(), '010106A901O'],
'Negative' => [v::not(v::hetu()), '010106A9012'],
'With template' => [v::hetu(), '010106A901O', 'That is not a HETU'],
'With name' => [v::hetu()->setName('Hetu'), '010106A901O'],
]);
?>
--EXPECT--
Default
⎺⎺⎺⎺⎺⎺⎺
"010106A901O" must be a valid Finnish personal identity code
- "010106A901O" must be a valid Finnish personal identity code
[
'hetu' => '"010106A901O" must be a valid Finnish personal identity code',
]
Negative
⎺⎺⎺⎺⎺⎺⎺⎺
"010106A9012" must not be a valid Finnish personal identity code
- "010106A9012" must not be a valid Finnish personal identity code
[
'hetu' => '"010106A9012" must not be a valid Finnish personal identity code',
]
With template
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
That is not a HETU
- That is not a HETU
[
'hetu' => 'That is not a HETU',
]
With name
⎺⎺⎺⎺⎺⎺⎺⎺⎺
Hetu must be a valid Finnish personal identity code
- Hetu must be a valid Finnish personal identity code
[
'Hetu' => 'Hetu must be a valid Finnish personal identity code',
]

View file

@ -0,0 +1,79 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Test\TestCase;
#[Group('rule')]
#[CoversClass(Hetu::class)]
final class HetuTest extends TestCase
{
#[Test]
#[DataProvider('providerForNonStringTypes')]
public function itShouldAlwaysInvalidateWhenValueIsNotString(mixed $input): void
{
self::assertInvalidInput(new Hetu(), $input);
}
#[Test]
#[DataProvider('providerForInvalidHetu')]
public function itShouldInvalidateInvalidHetu(string $input): void
{
self::assertInvalidInput(new Hetu(), $input);
}
#[Test]
#[DataProvider('providerForValidHetu')]
public function itShouldValidateValidHetu(string $input): void
{
self::assertValidInput(new Hetu(), $input);
}
/** @return array<array{string}> */
public static function providerForValidHetu(): array
{
return [
['010106A9012'],
['290199-907A'],
['010199+9012'],
['280291+913L'],
['280291+923X'],
];
}
/** @return array<array{string}> */
public static function providerForInvalidHetu(): array
{
return [
['010106a9012'],
['010106_9012'],
['010106G9012'],
['010106Z9012'],
['010106A901G'],
['010106A901I'],
['010106A901O'],
['010106A901Q'],
['010106A901Z'],
['010106!9012'],
['010106'],
['01X199+9012'],
['01X199Z9012'],
['01X199T9012'],
['999999A9999'],
['999999A999F'],
['300201A1236'],
['290201A123J'],
];
}
}