summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Zhineng <[email protected]>2025-05-13 10:32:08 +0800
committerLi Zhineng <[email protected]>2025-05-13 10:32:08 +0800
commitf451ac56a4db706fc3f45410be32d35201c8d431 (patch)
treefd854f38b7acccb84efa9d29837ce5d72ed11462
parent54bf2e23550c784bd650b99c4c8f7f824a5be1a5 (diff)
downloadvehicle-license-china-f451ac56a4db706fc3f45410be32d35201c8d431.tar.gz
vehicle-license-china-f451ac56a4db706fc3f45410be32d35201c8d431.zip
support suffix
-rw-r--r--src/RegistrationNumber.php98
-rw-r--r--tests/RegistrationNumberTest.php37
2 files changed, 116 insertions, 19 deletions
diff --git a/src/RegistrationNumber.php b/src/RegistrationNumber.php
index 3e284ed..a94c21b 100644
--- a/src/RegistrationNumber.php
+++ b/src/RegistrationNumber.php
@@ -141,6 +141,24 @@ final readonly class RegistrationNumber
'D', 'A', 'B', 'C', 'E',
];
+ /**
+ * @var string[]
+ */
+ private const array SPECIAL_SUFFIXS = [
+ '领', '使', '警', '学', '挂', '港', '澳', '试', '超',
+ ];
+
+ /**
+ * @var string[]
+ */
+ private const array GUANGDONG_Z_SPECIAL_SUFFIXS = [
+ '港', '澳',
+ ];
+
+ private const string GUANGDONG_PROVINCE = '粤';
+
+ private const string GUANGDONG_SPECIAL_AUTHORITY = 'Z';
+
public string $region;
public string $authority;
@@ -155,45 +173,87 @@ final readonly class RegistrationNumber
public static function make(string $registrationNumber): static
{
- static::validateAuthority($registrationNumber);
- static::validateSequence($registrationNumber);
+ [$region, $authority, $sequence, $suffix] = static::extractComponents($registrationNumber);
+
+ if (! static::checkAuthority($region, $authority)) {
+ static::notValid($registrationNumber);
+ }
+
+ if (! static::checkSequence($sequence)) {
+ static::notValid($registrationNumber);
+ }
+
+ if (! static::checkSuffix($region, $authority, $sequence, $suffix)) {
+ static::notValid($registrationNumber);
+ }
return new self($registrationNumber);
}
- private static function validateAuthority(string $registrationNumber): void
+ /**
+ * @return string[]
+ */
+ private static function extractComponents(string $registrationNumber): array
+ {
+ $normalized = mb_strtoupper($registrationNumber);
+
+ $region = mb_substr($normalized, 0, 1);
+ $authority = mb_substr($normalized, 1, 1);
+
+ $last = mb_substr($normalized, -1);
+
+ $suffix = in_array($last, self::SPECIAL_SUFFIXS, strict: true)
+ ? $last
+ : '';
+
+ $sequence = $suffix === ''
+ ? mb_substr($normalized, 2)
+ : mb_substr($normalized, 2, -1);
+
+ return [
+ $region, $authority, $sequence, $suffix,
+ ];
+ }
+
+ private static function checkAuthority(string $region, string $authority): bool
{
- $region = mb_substr($registrationNumber, 0, 1);
$authorities = self::AUTHORITIES[$region] ?? [];
if ($authorities === []) {
- static::notValid($registrationNumber);
+ return false;
}
- $code = mb_substr($registrationNumber, 1, 1);
-
- if ($code === 'O') {
- return;
+ if ($authority === 'O') {
+ return true;
}
- if (! in_array($code, $authorities)) {
- static::notValid($registrationNumber);
- }
+ return in_array($authority, $authorities, strict: true);
}
- private static function validateSequence(string $registrationNumber): void
+ private static function checkSequence(string $sequence): bool
{
- $sequence = mb_strtoupper(mb_substr($registrationNumber, 2));
-
- $valid = match (mb_strlen($sequence)) {
+ return match (mb_strlen($sequence)) {
4, 5 => static::checkSequenceForGeneral($sequence),
6 => static::checkSequenceForCleanEnergy($sequence),
- default => static::notValid($registrationNumber),
+ default => false,
};
+ }
- if (! $valid) {
- static::notValid($registrationNumber);
+ private static function checkSuffix(
+ string $region, string $authority, string $sequence, string $suffix
+ ): bool {
+ if (in_array($suffix, self::GUANGDONG_Z_SPECIAL_SUFFIXS, strict: true)) {
+ return $region === self::GUANGDONG_PROVINCE
+ && $authority === self::GUANGDONG_SPECIAL_AUTHORITY;
}
+
+ if ($suffix !== '' && ! in_array($suffix, self::SPECIAL_SUFFIXS, strict: true)) {
+ $len = mb_strlen($sequence);
+
+ return $len >= 4 && $len <= 5;
+ }
+
+ return true;
}
private static function checkSequenceForGeneral(string $sequence): bool
diff --git a/tests/RegistrationNumberTest.php b/tests/RegistrationNumberTest.php
index 5eb9f28..53a0ff9 100644
--- a/tests/RegistrationNumberTest.php
+++ b/tests/RegistrationNumberTest.php
@@ -16,6 +16,7 @@ final class RegistrationNumberTest extends TestCase
#[DataProvider('provide_valid_registration_numbers')]
#[DataProvider('provide_valid_clean_energy_registration_numbers')]
#[DataProvider('provide_valid_large_clean_energy_vehicle_registration_numbers')]
+ #[DataProvider('provide_valid_special_registration_numbers')]
public function test_valid_registration_number(string $registrationNumber): void
{
$instance = RegistrationNumber::make($registrationNumber);
@@ -25,6 +26,7 @@ final class RegistrationNumberTest extends TestCase
#[DataProvider('provide_invalid_registration_numbers')]
#[DataProvider('provide_invalid_clean_energy_registration_numbers')]
#[DataProvider('provide_invalid_large_clean_energy_vehicle_registration_numbers')]
+ #[DataProvider('provide_invalid_special_registration_numbers')]
public function test_invalid_registration_number(string $registrationNumber): void
{
$this->expectException(RegistrationNumberException::class);
@@ -250,4 +252,39 @@ final class RegistrationNumberTest extends TestCase
['粤ED1234D'],
];
}
+
+ /**
+ * @return string[][]
+ */
+ public static function provide_valid_special_registration_numbers(): array
+ {
+ return [
+ ['京A0006警'],
+ ['京A00006警'],
+ ['粤E0000学'],
+ ['粤E00000学'],
+ ['粤E0000挂'],
+ ['粤E00000挂'],
+ ['粤Z0000港'],
+ ['粤Z00000港'],
+ ['粤Z0000澳'],
+ ['粤Z00000澳'],
+ ['粤Z0000试'],
+ ['粤Z00000试'],
+ ['粤Z0000超'],
+ ['粤Z00000超'],
+ ];
+ }
+
+ /**
+ * @return string[][]
+ */
+ public static function provide_invalid_special_registration_numbers(): array
+ {
+ return [
+ ['粤E0006假'],
+ ['粤E0006港'],
+ ['粤E0000澳'],
+ ];
+ }
}