From 9f72d0fe64b8a609651caef3ff8da7c1518d9b47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 01:46:25 +0000 Subject: [PATCH 1/4] Initial plan From fd73e5bd02e037e00a90f2356fdfecf2c6d275c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 01:57:50 +0000 Subject: [PATCH 2/4] Integrate giggsey/libphonenumber-for-php for international phone number handling Co-authored-by: overtrue <1472352+overtrue@users.noreply.github.com> --- composer.json | 3 +- src/PhoneNumber.php | 64 +++++++++++++++++++++++++++++++++++++-- tests/PhoneNumberTest.php | 49 ++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0197280..caf8927 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "require": { "guzzlehttp/guzzle": "^6.2 || ^7.0", "php": ">=8.0", - "ext-json": "*" + "ext-json": "*", + "giggsey/libphonenumber-for-php": "^9.0" }, "require-dev": { "phpunit/phpunit": "^9.5.8", diff --git a/src/PhoneNumber.php b/src/PhoneNumber.php index c938c61..758ccdc 100644 --- a/src/PhoneNumber.php +++ b/src/PhoneNumber.php @@ -11,6 +11,10 @@ namespace Overtrue\EasySms; +use libphonenumber\NumberParseException; +use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberUtil; + /** * Class PhoneNumberInterface. * @@ -22,13 +26,58 @@ class PhoneNumber implements Contracts\PhoneNumberInterface protected ?int $IDDCode; + protected ?\libphonenumber\PhoneNumber $phoneNumberObject = null; + + protected PhoneNumberUtil $phoneUtil; + /** * PhoneNumberInterface constructor. */ public function __construct(int|string $numberWithoutIDDCode, ?string $IDDCode = null) { - $this->number = $numberWithoutIDDCode; - $this->IDDCode = $IDDCode ? intval(ltrim($IDDCode, '+0')) : null; + $this->phoneUtil = PhoneNumberUtil::getInstance(); + $numberStr = (string) $numberWithoutIDDCode; + $parsedIDDCode = $IDDCode ? intval(ltrim($IDDCode, '+0')) : null; + + // Try to parse using libphonenumber + try { + if (null !== $parsedIDDCode) { + // If IDD code is provided, construct the phone number directly + $this->phoneNumberObject = new \libphonenumber\PhoneNumber(); + $this->phoneNumberObject->setCountryCode($parsedIDDCode); + $this->phoneNumberObject->setNationalNumber($numberStr); + + $this->IDDCode = $parsedIDDCode; + $this->number = is_numeric($numberWithoutIDDCode) && is_int($numberWithoutIDDCode) ? $numberWithoutIDDCode : $numberStr; + } elseif (str_starts_with($numberStr, '+')) { + // International format with + + $this->phoneNumberObject = $this->phoneUtil->parse($numberStr, null); + $this->IDDCode = $this->phoneNumberObject->getCountryCode(); + $this->number = $this->phoneNumberObject->getNationalNumber(); + } elseif (str_starts_with($numberStr, '00')) { + // International format with 00 prefix - need to provide a region for parsing + // Try parsing with common regions + $this->phoneNumberObject = $this->phoneUtil->parse($numberStr, 'CN'); + $this->IDDCode = $this->phoneNumberObject->getCountryCode(); + $this->number = $this->phoneNumberObject->getNationalNumber(); + } else { + // No IDD code provided and no international prefix + // Keep the number as-is without parsing (backward compatibility) + $this->number = $numberWithoutIDDCode; + $this->IDDCode = null; + + // But still try to parse for validation purposes + try { + $this->phoneNumberObject = $this->phoneUtil->parse($numberStr, 'CN'); + } catch (NumberParseException $e) { + // Ignore parsing errors for backward compatibility + } + } + } catch (NumberParseException $e) { + // If parsing fails, fall back to storing the raw values + $this->number = $numberWithoutIDDCode; + $this->IDDCode = $parsedIDDCode; + } } /** @@ -52,6 +101,10 @@ public function getNumber(): int|string */ public function getUniversalNumber(): string { + if (null !== $this->phoneNumberObject && null !== $this->IDDCode && $this->phoneUtil->isValidNumber($this->phoneNumberObject)) { + return $this->phoneUtil->format($this->phoneNumberObject, PhoneNumberFormat::E164); + } + return $this->getPrefixedIDDCode('+').$this->number; } @@ -60,6 +113,13 @@ public function getUniversalNumber(): string */ public function getZeroPrefixedNumber(): string { + if (null !== $this->phoneNumberObject && null !== $this->IDDCode) { + $e164 = $this->phoneUtil->format($this->phoneNumberObject, PhoneNumberFormat::E164); + + // Convert +XX to 00XX + return '00'.substr($e164, 1); + } + return $this->getPrefixedIDDCode('00').$this->number; } diff --git a/tests/PhoneNumberTest.php b/tests/PhoneNumberTest.php index 6cd030b..fbf5fdd 100644 --- a/tests/PhoneNumberTest.php +++ b/tests/PhoneNumberTest.php @@ -47,4 +47,53 @@ public function testJsonEncode() $n = new PhoneNumber(18888888888, 68); $this->assertSame(json_encode(['number' => $n->getUniversalNumber()]), \json_encode(['number' => $n])); } + + public function testInternationalFormat() + { + // Test international format with + + $n = new PhoneNumber('+8618888888888'); + $this->assertSame(86, $n->getIDDCode()); + $this->assertSame('18888888888', $n->getNumber()); + $this->assertSame('+8618888888888', $n->getUniversalNumber()); + $this->assertSame('008618888888888', $n->getZeroPrefixedNumber()); + } + + public function testInternationalFormatWithoutPlus() + { + // Test international format starting with 00 + $n = new PhoneNumber('008618888888888'); + $this->assertSame(86, $n->getIDDCode()); + $this->assertSame('18888888888', $n->getNumber()); + $this->assertSame('+8618888888888', $n->getUniversalNumber()); + } + + public function testDifferentCountries() + { + // Test US number + $n = new PhoneNumber('+1 650 253 0000'); + $this->assertSame(1, $n->getIDDCode()); + $this->assertSame('+16502530000', $n->getUniversalNumber()); + + // Test Netherlands number + $n = new PhoneNumber('+31612345678'); + $this->assertSame(31, $n->getIDDCode()); + $this->assertSame('+31612345678', $n->getUniversalNumber()); + + // Test UK number + $n = new PhoneNumber('+44 117 496 0123'); + $this->assertSame(44, $n->getIDDCode()); + $this->assertSame('+441174960123', $n->getUniversalNumber()); + } + + public function testChineseMainlandCheck() + { + $n = new PhoneNumber(18888888888); + $this->assertTrue($n->inChineseMainland()); + + $n = new PhoneNumber(18888888888, 86); + $this->assertTrue($n->inChineseMainland()); + + $n = new PhoneNumber(18888888888, 1); + $this->assertFalse($n->inChineseMainland()); + } } From e0a9c1d2b726705b10314c16137a1e65e26bd9d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 03:08:14 +0000 Subject: [PATCH 3/4] Update minimum PHP version requirement to 8.4 Co-authored-by: overtrue <1472352+overtrue@users.noreply.github.com> --- README.md | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5ed8f0..73a66c3 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ ## 环境需求 -- PHP >= 5.6 +- PHP >= 8.4 ## 安装 diff --git a/composer.json b/composer.json index caf8927..dd8cfa7 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "require": { "guzzlehttp/guzzle": "^6.2 || ^7.0", - "php": ">=8.0", + "php": ">=8.4", "ext-json": "*", "giggsey/libphonenumber-for-php": "^9.0" }, From db2d5aafabe121a2a21b73a0c515f3c6a8d12cbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 03:14:31 +0000 Subject: [PATCH 4/4] Update GitHub Actions workflow to use PHP 8.4 Co-authored-by: overtrue <1472352+overtrue@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1bcca0a..3595ad0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ jobs: phpunit: strategy: matrix: - php_version: [8.0, 8.1, 8.2, 8.3] + php_version: [8.4] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2