Skip to content

Commit

Permalink
#16 Implement automatic detection of country
Browse files Browse the repository at this point in the history
  • Loading branch information
Propaganistas committed Sep 15, 2015
1 parent 053677e commit 43b2809
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 9 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.:

Expand All @@ -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',
Expand All @@ -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
Expand Down
32 changes: 25 additions & 7 deletions src/PhoneValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
Expand Down
13 changes: 13 additions & 0 deletions tests/TestPhoneValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 43b2809

Please sign in to comment.