diff options
| author | Li Zhineng <[email protected]> | 2025-05-13 20:16:21 +0800 |
|---|---|---|
| committer | Li Zhineng <[email protected]> | 2025-05-13 20:16:21 +0800 |
| commit | b22f622c8e8d41aa477a075c0b22804a7f27092a (patch) | |
| tree | 12385af4765a6fccd65fddb9769f1fb8041e947e | |
| parent | f451ac56a4db706fc3f45410be32d35201c8d431 (diff) | |
| download | vehicle-license-china-b22f622c8e8d41aa477a075c0b22804a7f27092a.tar.gz vehicle-license-china-b22f622c8e8d41aa477a075c0b22804a7f27092a.zip | |
support embassy and consulate registration numbers
| -rw-r--r-- | src/RegistrationNumber.php | 159 | ||||
| -rw-r--r-- | tests/RegistrationNumberTest.php | 57 |
2 files changed, 208 insertions, 8 deletions
diff --git a/src/RegistrationNumber.php b/src/RegistrationNumber.php index a94c21b..346a1c9 100644 --- a/src/RegistrationNumber.php +++ b/src/RegistrationNumber.php @@ -159,6 +159,10 @@ final readonly class RegistrationNumber private const string GUANGDONG_SPECIAL_AUTHORITY = 'Z'; + private const string EMBASSY_SUFFIX = '使'; + + private const string CONSULATE_SUFFIX = '领'; + public string $region; public string $authority; @@ -173,6 +177,90 @@ final readonly class RegistrationNumber public static function make(string $registrationNumber): static { + static::validate($registrationNumber); + + return new self($registrationNumber); + } + + private static function validate(string $registrationNumber): void + { + $registrationNumber = mb_strtoupper($registrationNumber); + $last = mb_substr($registrationNumber, -1); + + match ($last) { + self::EMBASSY_SUFFIX => static::validateEmbassyRegistrationNumber($registrationNumber), + self::CONSULATE_SUFFIX => static::validateConsulateRegistrationNumber($registrationNumber), + default => static::validateRegistrationNumber($registrationNumber), + }; + } + + private static function validateEmbassyRegistrationNumber(string $registrationNumber): void + { + if (! static::checkEmbassyRegistrationNumberLength($registrationNumber)) { + static::notValid($registrationNumber); + } + + [$agencyNumber, $sequence, $suffix] = static::extractEmbassyComponents($registrationNumber); + + if (! static::checkAgencyNumber($agencyNumber)) { + static::notValid($registrationNumber); + } + + if (! static::checkEmbassySequence($sequence)) { + static::notValid($registrationNumber); + } + + if ($suffix !== self::EMBASSY_SUFFIX) { + static::notValid($registrationNumber); + } + } + + private static function validateConsulateRegistrationNumber(string $registrationNumber): void + { + if (! static::checkConsulateRegistrationNumberLength($registrationNumber)) { + static::notValid($registrationNumber); + } + + [$region, $agencyNumber, $sequence, $suffix] = static::extractConsulateComponents($registrationNumber); + + if (! isset(self::AUTHORITIES[$region])) { + static::notValid($registrationNumber); + } + + if (! static::checkAgencyNumber($agencyNumber)) { + static::notValid($registrationNumber); + } + + if (! static::checkConsulateSequence($sequence)) { + static::notValid($registrationNumber); + } + + if ($suffix !== self::CONSULATE_SUFFIX) { + static::notValid($registrationNumber); + } + } + + private static function checkEmbassyRegistrationNumberLength(string $registrationNumber): bool + { + $agencyNumber = 3; + $sequence = 3; + $suffix = 1; + + return mb_strlen($registrationNumber) === $agencyNumber + $sequence + $suffix; + } + + private static function checkConsulateRegistrationNumberLength(string $registrationNumber): bool + { + $region = 1; + $agencyNumber = 3; + $sequence = 2; + $suffix = 1; + + return mb_strlen($registrationNumber) === $region + $agencyNumber + $sequence + $suffix; + } + + private static function validateRegistrationNumber(string $registrationNumber): void + { [$region, $authority, $sequence, $suffix] = static::extractComponents($registrationNumber); if (! static::checkAuthority($region, $authority)) { @@ -186,35 +274,61 @@ final readonly class RegistrationNumber if (! static::checkSuffix($region, $authority, $sequence, $suffix)) { static::notValid($registrationNumber); } + } - return new self($registrationNumber); + /** + * @return string[] + */ + private static function extractEmbassyComponents(string $registrationNumber): array + { + $agency = mb_substr($registrationNumber, 0, 3); + $sequence = mb_substr($registrationNumber, 3, 3); + $suffix = mb_substr($registrationNumber, 6, 1); + + return [$agency, $sequence, $suffix]; } /** * @return string[] */ - private static function extractComponents(string $registrationNumber): array + private static function extractConsulateComponents(string $registrationNumber): array { - $normalized = mb_strtoupper($registrationNumber); + $region = mb_substr($registrationNumber, 0, 1); + $agencyNumber = mb_substr($registrationNumber, 1, 3); + $sequence = mb_substr($registrationNumber, 4, 2); + $suffix = mb_substr($registrationNumber, 6, 1); - $region = mb_substr($normalized, 0, 1); - $authority = mb_substr($normalized, 1, 1); + return [$region, $agencyNumber, $sequence, $suffix]; + } - $last = mb_substr($normalized, -1); + /** + * @return string[] + */ + private static function extractComponents(string $registrationNumber): array + { + $region = mb_substr($registrationNumber, 0, 1); + $authority = mb_substr($registrationNumber, 1, 1); + + $last = mb_substr($registrationNumber, -1); $suffix = in_array($last, self::SPECIAL_SUFFIXS, strict: true) ? $last : ''; $sequence = $suffix === '' - ? mb_substr($normalized, 2) - : mb_substr($normalized, 2, -1); + ? mb_substr($registrationNumber, 2) + : mb_substr($registrationNumber, 2, -1); return [ $region, $authority, $sequence, $suffix, ]; } + private static function checkAgencyNumber(string $agencyNumber): bool + { + return preg_match('/^\d{3}$/', $agencyNumber) === 1; + } + private static function checkAuthority(string $region, string $authority): bool { $authorities = self::AUTHORITIES[$region] ?? []; @@ -256,6 +370,35 @@ final readonly class RegistrationNumber return true; } + private static function checkEmbassySequence(string $sequence): bool + { + return preg_match('/^\d{3}$/', $sequence) === 1; + } + + private static function checkConsulateSequence(string $sequence): bool + { + $len = mb_strlen($sequence); + + if ($len !== 2) { + return false; + } + + $first = mb_ord($sequence[0]); + $last = mb_ord($sequence[1]); + + if ($first >= ord('0') && $first <= ord('9') + && $last >= ord('0') && $last <= ord('9')) { + return true; + } + + if ($first >= ord('0') && $first <= ord('9') + && $last >= ord('A') && $last <= ord('Z')) { + return $last !== ord('O') && $last !== ord('I'); + } + + return false; + } + private static function checkSequenceForGeneral(string $sequence): bool { $letters = 0; diff --git a/tests/RegistrationNumberTest.php b/tests/RegistrationNumberTest.php index 53a0ff9..b014072 100644 --- a/tests/RegistrationNumberTest.php +++ b/tests/RegistrationNumberTest.php @@ -17,6 +17,8 @@ final class RegistrationNumberTest extends TestCase #[DataProvider('provide_valid_clean_energy_registration_numbers')] #[DataProvider('provide_valid_large_clean_energy_vehicle_registration_numbers')] #[DataProvider('provide_valid_special_registration_numbers')] + #[DataProvider('provide_valid_embassy_registration_numbers')] + #[DataProvider('provide_valid_consulate_registration_numbers')] public function test_valid_registration_number(string $registrationNumber): void { $instance = RegistrationNumber::make($registrationNumber); @@ -27,6 +29,8 @@ final class RegistrationNumberTest extends TestCase #[DataProvider('provide_invalid_clean_energy_registration_numbers')] #[DataProvider('provide_invalid_large_clean_energy_vehicle_registration_numbers')] #[DataProvider('provide_invalid_special_registration_numbers')] + #[DataProvider('provide_invalid_embassy_registration_numbers')] + #[DataProvider('provide_invalid_consulate_registration_numbers')] public function test_invalid_registration_number(string $registrationNumber): void { $this->expectException(RegistrationNumberException::class); @@ -287,4 +291,57 @@ final class RegistrationNumberTest extends TestCase ['粤E0000澳'], ]; } + + /** + * @return string[][] + */ + public static function provide_valid_embassy_registration_numbers(): array + { + return [ + ['224578使'], + ]; + } + + /** + * @return string[][] + */ + public static function provide_invalid_embassy_registration_numbers(): array + { + return [ + ['22A578使'], + ['224A78使'], + ['2245A8使'], + ['22457A使'], + ['0224578使'], + ['224578使使'], + ]; + } + + /** + * @return string[][] + */ + public static function provide_valid_consulate_registration_numbers(): array + { + return [ + ['沪22478领'], + ['沪2247A领'], + ]; + } + + /** + * @return string[][] + */ + public static function provide_invalid_consulate_registration_numbers(): array + { + return [ + ['假22478领'], + ['沪22-78领'], + ['沪224A8领'], + ['沪224O8领'], + ['沪2247O领'], + ['沪2247I领'], + ['沪022478领'], + ['沪22478领领'], + ]; + } } |
