summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Zhineng <[email protected]>2025-05-13 20:16:21 +0800
committerLi Zhineng <[email protected]>2025-05-13 20:16:21 +0800
commitb22f622c8e8d41aa477a075c0b22804a7f27092a (patch)
tree12385af4765a6fccd65fddb9769f1fb8041e947e
parentf451ac56a4db706fc3f45410be32d35201c8d431 (diff)
downloadvehicle-license-china-b22f622c8e8d41aa477a075c0b22804a7f27092a.tar.gz
vehicle-license-china-b22f622c8e8d41aa477a075c0b22804a7f27092a.zip
support embassy and consulate registration numbers
-rw-r--r--src/RegistrationNumber.php159
-rw-r--r--tests/RegistrationNumberTest.php57
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领领'],
+ ];
+ }
}