From 43b28096f88c0a3191a2cc123ca84da179e47fc7 Mon Sep 17 00:00:00 2001 From: Propaganistas Date: Tue, 15 Sep 2015 16:24:09 +0200 Subject: [PATCH] #16 Implement automatic detection of country --- README.md | 12 ++++++++++-- src/PhoneValidator.php | 32 +++++++++++++++++++++++++------- tests/TestPhoneValidator.php | 13 +++++++++++++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ceec6b9..db45954 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Adds a phone validator to Laravel 4 and 5 based on the [PHP port](https://github ### Usage -To validate a field using the phone validator, use the `phone` keyword in your validation rules array. The phone validator is able to operate in two ways. +To validate a field using the phone validator, use the `phone` keyword in your validation rules array. The phone validator is able to operate in **three** ways. - You either specify [*ISO 3166-1 alpha-2 compliant*](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements) country codes yourself as parameters for the validator, e.g.: @@ -63,7 +63,7 @@ To validate a field using the phone validator, use the `phone` keyword in your v The validator will check if the number is valid in at least one of provided countries, so feel free to add as many country codes as you like. -- Or you don't specify any parameters but you plug in a dedicated country input field (keyed by *ISO 3166-1 compliant* country codes) to allow end users to supply a country on their own. The easiest method by far is to install the [CountryList package by monarobase](https://github.com/Monarobase/country-list). The country field has to be named similar to the phone field but with `_country` appended: +- You don't specify any parameters but you plug in a dedicated country input field (keyed by *ISO 3166-1 compliant* country codes) to allow end users to supply a country on their own. The easiest method by far is to install the [CountryList package by monarobase](https://github.com/Monarobase/country-list). The country field has to be named similar to the phone field but with `_country` appended: ```php 'phonefield' => 'phone', @@ -76,6 +76,14 @@ To validate a field using the phone validator, use the `phone` keyword in your v Countries::getList(App::getLocale(), 'php', 'cldr')) ``` +- You instruct the validator to detect which country the number belongs to using the `AUTO` keyword: + + ```php +'phonefield' => 'phone:AUTO', + ``` + + The validator will try to extract the country from the number itself and then check if the number is valid for that country. Note that this will only work when phone numbers are entered in *international format* (prefixed with a `+` sign, e.g. +32 ....). Leading double zeros will **NOT** be parsed correctly as this isn't an established consistency. + To specify constraints on the number type, just append the allowed types to the end of the parameters, e.g.: ```php diff --git a/src/PhoneValidator.php b/src/PhoneValidator.php index 2e9a764..c6b1d64 100755 --- a/src/PhoneValidator.php +++ b/src/PhoneValidator.php @@ -57,17 +57,30 @@ public function validatePhone($attribute, $value, $parameters, $validator) $this->determineTypes(); $this->checkLeftoverParameters(); - // Perform validation. $phoneUtil = PhoneNumberUtil::getInstance(); - + + // Perform validation. foreach ($this->allowedCountries as $country) { try { + // For default countries or country field, the following throws NumberParseException if + // not parsed correctly against the supplied country. + // For automatic detection: tries to discover the country code using from the number itself. $phoneProto = $phoneUtil->parse($value, $country); - if ($phoneUtil->isValidNumberForRegion($phoneProto, $country) - && (empty($this->allowedTypes) || in_array($phoneUtil->getNumberType($phoneProto), $this->allowedTypes)) - ) { - return true; + + // For automatic detection, the number should have a country code. + // Check if type is allowed. + if ($phoneProto->hasCountryCode() && empty($this->allowedTypes) || in_array($phoneUtil->getNumberType($phoneProto), $this->allowedTypes)) { + + // Automatic detection: + if ($country == 'ZZ') { + // Validate if the international phone number is valid for its contained country. + return $phoneUtil->isValidNumber($phoneProto); + } + + // Force validation of number against the specified country. + return $phoneUtil->isValidNumberForRegion($phoneProto, $country); } + } catch (NumberParseException $e) { // Proceed to default validation error. } @@ -126,6 +139,10 @@ protected function determineCountries() $this->allowedCountries = ($this->isPhoneCountry($this->data[$field])) ? array($this->data[$field]) : array(); // No exception should be thrown since empty country fields should validate to false. } + // Or if we need to parse for automatic detection. + elseif (in_array('AUTO', $this->parameters)) { + $this->allowedCountries = array('ZZ'); + } // Else use the supplied parameters. else { $this->allowedCountries = array_filter($this->parameters, function($item) { @@ -166,7 +183,8 @@ protected function determineTypes() */ protected function checkLeftoverParameters() { - $leftovers = array_diff($this->parameters, $this->allowedCountries, $this->untransformedTypes); + // Remove the automatic detection option if applicable. + $leftovers = array_diff($this->parameters, $this->allowedCountries, $this->untransformedTypes, array('AUTO')); if (!empty($leftovers)) { throw new InvalidParameterException(implode(', ', $leftovers)); } diff --git a/tests/TestPhoneValidator.php b/tests/TestPhoneValidator.php index a6ba577..5bf33fd 100644 --- a/tests/TestPhoneValidator.php +++ b/tests/TestPhoneValidator.php @@ -101,6 +101,19 @@ public function testValidatePhoneWithCountryFieldWithType() $this->assertFalse($this->performValidation(['value' => '016123456', 'params' => 'mobile', 'country' => 'NL'])); } + + public function testValidatePhoneAutomaticDetectionFromInternationalInput() + { + // Validator with correct international input. + $this->assertTrue($this->performValidation(['value' => '+3216123456', 'params' => 'AUTO'])); + + // Validator with wrong international input. + $this->assertFalse($this->performValidation(['value' => '003216123456', 'params' => 'AUTO'])); + + // Validator with wrong international input. + $this->assertFalse($this->performValidation(['value' => '+321456', 'params' => 'AUTO'])); + } + public function testValidatePhoneNoDefaultCountryNoCountryField() { $this->setExpectedException('Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException');