Use libphonenumber
Doing regex on phone numbers is not a great idea. This is a breaking change, but a good one. Phone validation is now much stricter, and allows choosing the country.
This commit is contained in:
parent
fc8230acef
commit
cc3bf86b2f
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -12,6 +12,18 @@ Deprecations:
|
|||
- Symfony façade validators are no longer supported and were
|
||||
removed.
|
||||
|
||||
Fixes:
|
||||
|
||||
- `KeySet` now reports which extra keys are causing the rule to fail.
|
||||
|
||||
Changes:
|
||||
|
||||
- You can no longer wrap `KeySet` in `Not`.
|
||||
- `Phone` now uses `giggsey/libphonenumber-for-php`, this package needs
|
||||
to be installed if you want to use this validator.
|
||||
- `Phone` now supports the parameter `$countryCode` to validate phones
|
||||
of a specific country.
|
||||
|
||||
## 2.2.4
|
||||
|
||||
Meta:
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^3.0",
|
||||
"giggsey/libphonenumber-for-php-lite": "^8.13",
|
||||
"malukenho/docheader": "^1.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
|
@ -38,7 +39,8 @@
|
|||
"ext-bcmath": "Arbitrary Precision Mathematics",
|
||||
"ext-fileinfo": "File Information",
|
||||
"ext-mbstring": "Multibyte String Functions",
|
||||
"egulias/email-validator": "Strict (RFC compliant) email validation"
|
||||
"egulias/email-validator": "Improves the Email rule if available",
|
||||
"giggsey/libphonenumber-for-php-lite": "Enables the phone rule if available"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -2,19 +2,15 @@
|
|||
|
||||
- `Phone()`
|
||||
|
||||
Validates whether the input is a valid phone number.
|
||||
Validates whether the input is a valid phone number. This rule requires
|
||||
the `giggsey/libphonenumber-for-php-lite` package.
|
||||
|
||||
Validates a valid 7, 10, 11 digit phone number (North America, Europe and most
|
||||
Asian and Middle East countries), supporting country and area codes (in dot,
|
||||
space or dashed notations) such as:
|
||||
|
||||
- (555)555-5555
|
||||
- 555 555 5555
|
||||
- +5(555)555.5555
|
||||
- 33(1)22 22 22 22
|
||||
- +33(1)22 22 22 22
|
||||
- +33(020)7777 7777
|
||||
- 03-6106666
|
||||
```php
|
||||
v::phone()->validate('+1 650 253 00 00'); // true
|
||||
v::phone('BR')->validate('+55 11 91111 1111'); // true
|
||||
v::phone('BR')->validate('11 91111 1111'); // false
|
||||
```
|
||||
|
||||
## Categorization
|
||||
|
||||
|
@ -24,6 +20,7 @@ space or dashed notations) such as:
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
2.3.0 | Updated to use external validator
|
||||
0.5.0 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -9,6 +9,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Exceptions;
|
||||
|
||||
use Respect\Validation\Helpers\CountryInfo;
|
||||
|
||||
/**
|
||||
* @author Danilo Correa <danilosilva87@gmail.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
|
@ -16,15 +18,37 @@ namespace Respect\Validation\Exceptions;
|
|||
*/
|
||||
final class PhoneException extends ValidationException
|
||||
{
|
||||
public const FOR_COUNTRY = 'for_country';
|
||||
public const INTERNATIONAL = 'international';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected $defaultTemplates = [
|
||||
self::MODE_DEFAULT => [
|
||||
self::STANDARD => '{{name}} must be a valid telephone number',
|
||||
self::INTERNATIONAL => '{{name}} must be a valid telephone number',
|
||||
self::FOR_COUNTRY => '{{name}} must be a valid telephone number for country {{countryName}}',
|
||||
],
|
||||
self::MODE_NEGATIVE => [
|
||||
self::STANDARD => '{{name}} must not be a valid telephone number',
|
||||
self::INTERNATIONAL => '{{name}} must not be a valid telephone number',
|
||||
self::FOR_COUNTRY => '{{name}} must not be a valid telephone number for country {{countryName}}',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function chooseTemplate(): string
|
||||
{
|
||||
$countryCode = $this->getParam('countryCode');
|
||||
|
||||
if (!$countryCode) {
|
||||
return self::INTERNATIONAL;
|
||||
}
|
||||
|
||||
$countryInfo = new CountryInfo($countryCode);
|
||||
$this->setParam('countryName', $countryInfo->getCountry());
|
||||
|
||||
return self::FOR_COUNTRY;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,11 @@ class ValidationException extends InvalidArgumentException implements Exception
|
|||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
public function setParam(string $name, mixed $value): void
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
}
|
||||
|
||||
public function updateMode(string $mode): void
|
||||
{
|
||||
$this->mode = $mode;
|
||||
|
|
|
@ -16,7 +16,7 @@ use function file_get_contents;
|
|||
use function json_decode;
|
||||
use function sprintf;
|
||||
|
||||
final class Subdivisions
|
||||
final class CountryInfo
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
|
@ -9,23 +9,50 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
|
||||
use function class_exists;
|
||||
use function is_null;
|
||||
use function is_scalar;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Validates whether the input is a valid phone number.
|
||||
*
|
||||
* Validates a valid 7, 10, 11 digit phone number (North America, Europe and
|
||||
* most Asian and Middle East countries), supporting country and area codes (in
|
||||
* dot,space or dashed notations)
|
||||
* Validates an international or country-specific telephone number
|
||||
*
|
||||
* @author Danilo Correa <danilosilva87@gmail.com>
|
||||
* @author Graham Campbell <graham@mineuk.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
* @author Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
final class Phone extends AbstractRule
|
||||
{
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
private $countryCode;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function __construct(?string $countryCode = null)
|
||||
{
|
||||
$this->countryCode = $countryCode;
|
||||
|
||||
if (!is_null($countryCode) && !(new CountryCode())->validate($countryCode)) {
|
||||
throw new ComponentException(
|
||||
sprintf(
|
||||
'Invalid country code %s',
|
||||
$countryCode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!class_exists(PhoneNumberUtil::class)) {
|
||||
throw new ComponentException('The phone validator requires giggsey/libphonenumber-for-php');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -35,16 +62,12 @@ final class Phone extends AbstractRule
|
|||
return false;
|
||||
}
|
||||
|
||||
return preg_match($this->getPregFormat(), (string) $input) > 0;
|
||||
}
|
||||
|
||||
private function getPregFormat(): string
|
||||
{
|
||||
return sprintf(
|
||||
'/^\+?(%1$s)? ?(?(?=\()(\(%2$s\) ?%3$s)|([. -]?(%2$s[. -]*)?%3$s))$/',
|
||||
'\d{0,3}',
|
||||
'\d{1,3}',
|
||||
'((\d{3,5})[. -]?(\d{4})|(\d{2}[. -]?){4})'
|
||||
);
|
||||
try {
|
||||
return PhoneNumberUtil::getInstance()->isValidNumber(
|
||||
PhoneNumberUtil::getInstance()->parse((string) $input, $this->countryCode)
|
||||
);
|
||||
} catch (NumberParseException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Helpers\Subdivisions;
|
||||
use Respect\Validation\Helpers\CountryInfo;
|
||||
|
||||
use function array_keys;
|
||||
|
||||
|
@ -32,14 +32,14 @@ final class SubdivisionCode extends AbstractSearcher
|
|||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $subdivisions;
|
||||
private $countryInfo;
|
||||
|
||||
public function __construct(string $countryCode)
|
||||
{
|
||||
$subdivisions = new Subdivisions($countryCode);
|
||||
$countryInfo = new CountryInfo($countryCode);
|
||||
|
||||
$this->countryName = $subdivisions->getCountry();
|
||||
$this->subdivisions = array_keys($subdivisions->getSubdivisions());
|
||||
$this->countryName = $countryInfo->getCountry();
|
||||
$this->countryInfo = array_keys($countryInfo->getSubdivisions());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,6 @@ final class SubdivisionCode extends AbstractSearcher
|
|||
*/
|
||||
protected function getDataSource(): array
|
||||
{
|
||||
return $this->subdivisions;
|
||||
return $this->countryInfo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ $work->countryCode = 61;
|
|||
$work->primary = true;
|
||||
|
||||
$personal = new stdClass();
|
||||
$personal->number = '+61.0406 464 890';
|
||||
$personal->number = '123';
|
||||
$personal->country = 61;
|
||||
$personal->primary = false;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ try {
|
|||
}
|
||||
|
||||
try {
|
||||
v::not(v::phone())->check('11977777777');
|
||||
v::not(v::phone())->check('+1 650 253 00 00');
|
||||
} catch (PhoneException $exception) {
|
||||
echo $exception->getMessage() . PHP_EOL;
|
||||
}
|
||||
|
@ -30,13 +30,13 @@ try {
|
|||
}
|
||||
|
||||
try {
|
||||
v::not(v::phone())->assert('+5 555 555 5555');
|
||||
v::not(v::phone())->assert('+55 11 91111 1111');
|
||||
} catch (NestedValidationException $exception) {
|
||||
echo $exception->getFullMessage() . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
"123" must be a valid telephone number
|
||||
"11977777777" must not be a valid telephone number
|
||||
"+1 650 253 00 00" must not be a valid telephone number
|
||||
- "(555)5555 555" must be a valid telephone number
|
||||
- "+5 555 555 5555" must not be a valid telephone number
|
||||
- "+55 11 91111 1111" must not be a valid telephone number
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\PhoneException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
/**
|
||||
|
@ -29,55 +30,12 @@ final class PhoneTest extends RuleTestCase
|
|||
*/
|
||||
public function providerForValidInput(): array
|
||||
{
|
||||
$sut = new Phone();
|
||||
|
||||
return [
|
||||
[$sut, '+5-555-555-5555'],
|
||||
[$sut, '+5 555 555 5555'],
|
||||
[$sut, '+5.555.555.5555'],
|
||||
[$sut, '5-555-555-5555'],
|
||||
[$sut, '5.555.555.5555'],
|
||||
[$sut, '5 555 555 5555'],
|
||||
[$sut, '555.555.5555'],
|
||||
[$sut, '555 555 5555'],
|
||||
[$sut, '555-555-5555'],
|
||||
[$sut, '555-5555555'],
|
||||
[$sut, '5(555)555.5555'],
|
||||
[$sut, '+5(555)555.5555'],
|
||||
[$sut, '+5(555)555 5555'],
|
||||
[$sut, '+5(555)555-5555'],
|
||||
[$sut, '+5(555)5555555'],
|
||||
[$sut, '(555)5555555'],
|
||||
[$sut, '(555)555.5555'],
|
||||
[$sut, '(555)555-5555'],
|
||||
[$sut, '(555) 555 5555'],
|
||||
[$sut, '55555555555'],
|
||||
[$sut, '5555555555'],
|
||||
[$sut, '+33(1)2222222'],
|
||||
[$sut, '+33(1)222 2222'],
|
||||
[$sut, '+33(1)222.2222'],
|
||||
[$sut, '+33(1)22 22 22 22'],
|
||||
[$sut, '33(1)2222222'],
|
||||
[$sut, '33(1)22222222'],
|
||||
[$sut, '33(1)22 22 22 22'],
|
||||
[$sut, '(020) 7476 4026'],
|
||||
[$sut, '33(020) 7777 7777'],
|
||||
[$sut, '33(020)7777 7777'],
|
||||
[$sut, '+33(020) 7777 7777'],
|
||||
[$sut, '+33(020)7777 7777'],
|
||||
[$sut, '03-6106666'],
|
||||
[$sut, '036106666'],
|
||||
[$sut, '+33(11) 97777 7777'],
|
||||
[$sut, '+3311977777777'],
|
||||
[$sut, '11977777777'],
|
||||
[$sut, '11 97777 7777'],
|
||||
[$sut, '(11) 97777 7777'],
|
||||
[$sut, '(11) 97777-7777'],
|
||||
[$sut, '555-5555'],
|
||||
[$sut, '5555555'],
|
||||
[$sut, '555.5555'],
|
||||
[$sut, '555 5555'],
|
||||
[$sut, '+1 (555) 555 5555'],
|
||||
[new Phone(), '+1 650 253 00 00'],
|
||||
[new Phone('BR'), '+55 11 91111 1111'],
|
||||
[new Phone('BR'), '11 91111 1111'], // no international prefix
|
||||
[new Phone('BR'), '+5511911111111'], // no whitespace
|
||||
[new Phone('BR'), '11911111111'], // no prefix, no whitespace
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -86,38 +44,29 @@ final class PhoneTest extends RuleTestCase
|
|||
*/
|
||||
public function providerForInvalidInput(): array
|
||||
{
|
||||
$sut = new Phone();
|
||||
|
||||
return [
|
||||
[$sut, ''],
|
||||
[$sut, '123'],
|
||||
[$sut, '(11- 97777-7777'],
|
||||
[$sut, '-11) 97777-7777'],
|
||||
[$sut, 's555-5555'],
|
||||
[$sut, '555-555'],
|
||||
[$sut, '555555'],
|
||||
[$sut, '555+5555'],
|
||||
[$sut, '(555)555555'],
|
||||
[$sut, '(555)55555'],
|
||||
[$sut, '+(555)555 555'],
|
||||
[$sut, '+5(555)555 555'],
|
||||
[$sut, '+5(555)555 555 555'],
|
||||
[$sut, '555)555 555'],
|
||||
[$sut, '+5(555)5555 555'],
|
||||
[$sut, '(555)55 555'],
|
||||
[$sut, '(555)5555 555'],
|
||||
[$sut, '+5(555)555555'],
|
||||
[$sut, '5(555)55 55555'],
|
||||
[$sut, '(5)555555'],
|
||||
[$sut, '+55(5)55 5 55 55'],
|
||||
[$sut, '+55(5)55 55 55 5'],
|
||||
[$sut, '+55(5)55 55 55'],
|
||||
[$sut, '+55(5)5555 555'],
|
||||
[$sut, '+55()555 5555'],
|
||||
[$sut, '03610666-5'],
|
||||
[$sut, 'text'],
|
||||
[$sut, "555\n5555"],
|
||||
[$sut, []],
|
||||
[new Phone(), '+1-650-253-00-0'],
|
||||
[new Phone('BR'), '+1 11 91111 1111'], // invalid + code for BR
|
||||
];
|
||||
}
|
||||
|
||||
public function testThrowsExceptionWithCountryName(): void
|
||||
{
|
||||
$phoneValidator = new Phone('BR');
|
||||
|
||||
$this->expectException(PhoneException::class);
|
||||
$this->expectExceptionMessage('"abc" must be a valid telephone number for country "Brazil"');
|
||||
|
||||
$phoneValidator->assert('abc');
|
||||
}
|
||||
|
||||
public function testThrowsExceptionForInternationalNumbers(): void
|
||||
{
|
||||
$phoneValidator = new Phone();
|
||||
|
||||
$this->expectException(PhoneException::class);
|
||||
$this->expectExceptionMessage('"abc" must be a valid telephone number');
|
||||
|
||||
$phoneValidator->assert('abc');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ use stdClass;
|
|||
|
||||
use function stream_context_create;
|
||||
use function tmpfile;
|
||||
use function xml_parser_create;
|
||||
|
||||
/**
|
||||
* @group rule
|
||||
|
@ -39,18 +38,6 @@ final class ResourceTypeTest extends RuleTestCase
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @requires PHP < 8.0
|
||||
*/
|
||||
public function itShouldTestXmlResource(): void
|
||||
{
|
||||
$rule = new ResourceType();
|
||||
|
||||
self::assertValidInput($rule, xml_parser_create());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue