laravel-phone/LICENSE 0000644 00000002070 15243417773 0010322 0 ustar 00 The MIT License (MIT)
Copyright (c) 2014 Propaganistas
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
laravel-phone/src/PhoneNumber.php 0000644 00000016175 15243417773 0013052 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone;
use Exception;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Macroable;
use JsonSerializable;
use libphonenumber\NumberParseException as libNumberParseException;
use libphonenumber\PhoneNumberFormat as libPhoneNumberFormat;
use libphonenumber\PhoneNumberType as libPhoneNumberType;
use libphonenumber\PhoneNumberUtil;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberCountry;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberFormat;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberType;
use Propaganistas\LaravelPhone\Exceptions\CountryCodeException;
use Propaganistas\LaravelPhone\Exceptions\NumberFormatException;
use Propaganistas\LaravelPhone\Exceptions\NumberParseException;
class PhoneNumber implements Jsonable, JsonSerializable
{
use Macroable;
protected ?string $number;
protected array $countries;
protected bool $lenient = false;
public function __construct(?string $number, $country = [])
{
$this->number = $number;
$this->countries = Arr::wrap($country);
}
public function getCountry(): string|null
{
// Try to detect the country first from the number itself.
try {
return PhoneNumberUtil::getInstance()->getRegionCodeForNumber(
PhoneNumberUtil::getInstance()->parse($this->number, 'ZZ')
);
} catch (libNumberParseException $e) {
}
// Only then iterate over the provided countries.
$sanitizedCountries = PhoneNumberCountry::sanitize($this->countries);
foreach ($sanitizedCountries as $country) {
try {
$libPhoneObject = PhoneNumberUtil::getInstance()->parse($this->number, $country);
} catch (libNumberParseException $e) {
continue;
}
if ($this->lenient) {
if (PhoneNumberUtil::getInstance()->isPossibleNumber($libPhoneObject, $country)) {
return strtoupper($country);
}
continue;
}
if (PhoneNumberUtil::getInstance()->isValidNumberForRegion($libPhoneObject, $country)) {
return PhoneNumberUtil::getInstance()->getRegionCodeForNumber($libPhoneObject);
}
}
return null;
}
public function isOfCountry($country): bool
{
$countries = PhoneNumberCountry::sanitize(Arr::wrap($country));
$instance = clone $this;
$instance->countries = $countries;
return in_array($instance->getCountry(), $countries);
}
public function getType($asValue = false): int|string
{
$type = PhoneNumberUtil::getInstance()->getNumberType($this->toLibPhoneObject());
return $asValue ? $type : PhoneNumberType::getHumanReadableName($type);
}
public function isOfType($type): bool
{
$types = PhoneNumberType::sanitize(Arr::wrap($type));
// Add the unsure type when applicable.
if (array_intersect([libPhoneNumberType::FIXED_LINE, libPhoneNumberType::MOBILE], $types)) {
$types[] = libPhoneNumberType::FIXED_LINE_OR_MOBILE;
}
return in_array($this->getType(true), $types, true);
}
public function format(string|int $format): string
{
$sanitizedFormat = PhoneNumberFormat::sanitize($format);
if (is_null($sanitizedFormat)) {
throw NumberFormatException::invalid($format);
}
return PhoneNumberUtil::getInstance()->format(
$this->toLibPhoneObject(),
$sanitizedFormat
);
}
public function formatInternational(): string
{
return $this->format(libPhoneNumberFormat::INTERNATIONAL);
}
public function formatNational(): string
{
return $this->format(libPhoneNumberFormat::NATIONAL);
}
public function formatE164(): string
{
return $this->format(libPhoneNumberFormat::E164);
}
public function formatRFC3966(): string
{
return $this->format(libPhoneNumberFormat::RFC3966);
}
public function formatForCountry($country): string
{
if (! PhoneNumberCountry::isValid($country)) {
throw CountryCodeException::invalid($country);
}
return PhoneNumberUtil::getInstance()->formatOutOfCountryCallingNumber(
$this->toLibPhoneObject(),
$country
);
}
public function formatForMobileDialingInCountry($country, $withFormatting = false): string
{
if (! PhoneNumberCountry::isValid($country)) {
throw CountryCodeException::invalid($country);
}
return PhoneNumberUtil::getInstance()->formatNumberForMobileDialing(
$this->toLibPhoneObject(),
$country,
$withFormatting
);
}
public function isValid(): bool
{
try {
if ($this->lenient) {
return PhoneNumberUtil::getInstance()->isPossibleNumber(
$this->toLibPhoneObject()
);
}
return PhoneNumberUtil::getInstance()->isValidNumberForRegion(
$this->toLibPhoneObject(),
$this->getCountry(),
);
} catch (NumberParseException $e) {
return false;
}
}
public function lenient($enable = true): static
{
$this->lenient = $enable;
return $this;
}
public function equals($number, $country = null): bool
{
try {
if (! $number instanceof static) {
$number = new static($number, $country);
}
return $this->formatE164() === $number->formatE164();
} catch (NumberParseException $e) {
return false;
}
}
public function notEquals($number, $country = null): bool
{
return ! $this->equals($number, $country);
}
public function getRawNumber(): string
{
return $this->number;
}
public function toLibPhoneObject()
{
try {
return PhoneNumberUtil::getInstance()->parse($this->number, $this->getCountry());
} catch (libNumberParseException $e) {
empty($this->countries)
? throw NumberParseException::countryRequired($this->number)
: throw NumberParseException::countryMismatch($this->number, $this->countries);
}
}
public function toJson($options = 0)
{
return json_encode($this->jsonSerialize(), $options);
}
public function jsonSerialize(): string
{
return $this->formatE164();
}
public function __serialize()
{
return ['number' => $this->formatE164()];
}
public function __unserialize(array $serialized)
{
$this->number = $serialized['number'];
}
public function __toString()
{
// Formatting the phone number could throw an exception, but __toString() doesn't cope well with that.
// Let's just return the original number in that case.
try {
return $this->formatE164();
} catch (Exception $e) {
return (string) $this->number;
}
}
}
laravel-phone/src/helpers.php 0000644 00000000515 15243417773 0012261 0 ustar 00 <?php
use Propaganistas\LaravelPhone\PhoneNumber;
if (! function_exists('phone')) {
function phone(?string $number, $country = [], $format = null)
{
$phone = new PhoneNumber($number, $country);
if (! is_null($format)) {
return $phone->format($format);
}
return $phone;
}
}
laravel-phone/src/Rules/Phone.php 0000644 00000010403 15243417773 0012757 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use Illuminate\Validation\Validator;
use libphonenumber\PhoneNumberType as libPhoneNumberType;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberCountry;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberType;
use Propaganistas\LaravelPhone\Exceptions\NumberParseException;
use Propaganistas\LaravelPhone\PhoneNumber;
class Phone implements Rule, ValidatorAwareRule
{
protected Validator $validator;
protected ?string $countryField = null;
protected array $countries = [];
protected array $types = [];
protected bool $international = false;
protected bool $lenient = false;
public function passes($attribute, $value)
{
$countries = PhoneNumberCountry::sanitize([
$this->getCountryFieldValue($attribute),
...$this->countries,
]);
$types = PhoneNumberType::sanitize($this->types);
try {
$phone = (new PhoneNumber($value, $countries))->lenient($this->lenient);
// Is the country within the allowed list (if applicable)?
if (! $this->international && ! empty($countries) && ! $phone->isOfCountry($countries)) {
return false;
}
// Is the type within the allowed list (if applicable)?
if (! empty($types) && ! $phone->isOfType($types)) {
return false;
}
return $phone->isValid();
} catch (NumberParseException $e) {
return false;
}
}
public function country($country)
{
$countries = is_array($country) ? $country : func_get_args();
$this->countries = array_merge($this->countries, $countries);
return $this;
}
public function countryField($name)
{
$this->countryField = $name;
return $this;
}
public function type($type)
{
$types = is_array($type) ? $type : func_get_args();
$this->types = array_merge($this->types, $types);
return $this;
}
public function mobile()
{
$this->type(libPhoneNumberType::MOBILE);
return $this;
}
public function fixedLine()
{
$this->type(libPhoneNumberType::FIXED_LINE);
return $this;
}
public function lenient()
{
$this->lenient = true;
return $this;
}
public function international()
{
$this->international = true;
return $this;
}
protected function getCountryFieldValue(string $attribute)
{
// Using Arr::get() enables support for nested data.
return Arr::get($this->validator->getData(), $this->countryField ?: $attribute.'_country');
}
protected function isDataKey($attribute): bool
{
// Using Arr::has() enables support for nested data.
return Arr::has($this->validator->getData(), $attribute);
}
public function setParameters($parameters)
{
$parameters = is_array($parameters) ? $parameters : func_get_args();
foreach ($parameters as $parameter) {
if (strcasecmp('lenient', $parameter) === 0) {
$this->lenient();
} elseif (strcasecmp('international', $parameter) === 0) {
$this->international();
} elseif (strcasecmp('mobile', $parameter) === 0) {
$this->mobile();
} elseif (strcasecmp('fixed_line', $parameter) === 0) {
$this->fixedLine();
} elseif ($this->isDataKey($parameter)) {
$this->countryField = $parameter;
} elseif (PhoneNumberCountry::isValid($parameter)) {
$this->country($parameter);
} elseif (ctype_digit($parameter) && PhoneNumberType::isValid((int) $parameter)) {
$this->type((int) $parameter);
} elseif (PhoneNumberType::isValidName($parameter)) {
$this->type($parameter);
}
}
return $this;
}
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
public function message()
{
return trans('validation.phone');
}
}
laravel-phone/src/PhoneServiceProvider.php 0000644 00000002241 15243417773 0014722 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;
use Illuminate\Validation\Rule;
use libphonenumber\PhoneNumberUtil;
class PhoneServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLibraryBinding();
$this->registerValidator();
}
public function registerLibraryBinding(): void
{
$this->app->singleton('libphonenumber', function ($app) {
return PhoneNumberUtil::getInstance();
});
$this->app->alias('libphonenumber', PhoneNumberUtil::class);
}
public function registerValidator(): void
{
$this->callAfterResolving('validator', function (Factory $validator) {
$validator->extendDependent('phone', function ($attribute, $value, array $parameters, $validator) {
return (new Rules\Phone)
->setValidator($validator)
->setParameters($parameters)
->passes($attribute, $value);
});
});
Rule::macro('phone', function () {
return new Rules\Phone;
});
}
}
laravel-phone/src/Casts/RawPhoneNumberCast.php 0000644 00000002077 15243417773 0015410 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Casts;
use InvalidArgumentException;
use Propaganistas\LaravelPhone\PhoneNumber;
class RawPhoneNumberCast extends PhoneNumberCast
{
public function get($model, string $key, $value, array $attributes)
{
if (! $value) {
return null;
}
$phone = new PhoneNumber($value,
$this->getPossibleCountries($key, $attributes)
);
$country = $phone->getCountry();
if ($country === null) {
throw new InvalidArgumentException('Missing country specification for '.$key.' attribute cast');
}
return new PhoneNumber($value, $country);
}
public function set($model, string $key, $value, array $attributes)
{
if ($value instanceof PhoneNumber) {
return $value->getRawNumber();
}
return (string) $value;
}
public function serialize($model, string $key, $value, array $attributes)
{
if (! $value) {
return null;
}
return $value->getRawNumber();
}
}
laravel-phone/src/Casts/PhoneNumberCast.php 0000644 00000001727 15243417773 0014737 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes;
use Illuminate\Support\Arr;
use Propaganistas\LaravelPhone\Aspects\PhoneNumberCountry;
abstract class PhoneNumberCast implements CastsAttributes, SerializesCastableAttributes
{
protected array $parameters;
public function __construct()
{
$this->parameters = func_get_args();
}
protected function getPossibleCountries($key, array $attributes): array
{
$parameters = array_map(function ($parameter) use ($attributes) {
if ($value = Arr::get($attributes, $parameter)) {
return $value;
}
return $parameter;
}, [...$this->parameters, $key.'_country']);
return array_filter($parameters, function ($parameter) {
return PhoneNumberCountry::isValid($parameter);
});
}
}
laravel-phone/src/Casts/E164PhoneNumberCast.php 0000644 00000002123 15243417773 0015266 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Casts;
use Propaganistas\LaravelPhone\PhoneNumber;
use UnexpectedValueException;
class E164PhoneNumberCast extends PhoneNumberCast
{
public function get($model, string $key, $value, array $attributes)
{
if (! $value) {
return null;
}
$phone = new PhoneNumber($value);
if ($phone->getCountry() === null) {
throw new UnexpectedValueException('Queried value for '.$key.' is not in international format');
}
return $phone;
}
public function set($model, string $key, $value, array $attributes)
{
if (! $value) {
return null;
}
if (! $value instanceof PhoneNumber) {
$value = new PhoneNumber($value,
$this->getPossibleCountries($key, $attributes)
);
}
return $value->formatE164();
}
public function serialize($model, string $key, $value, array $attributes)
{
if (! $value) {
return null;
}
return $value->formatE164();
}
}
laravel-phone/src/Aspects/PhoneNumberType.php 0000644 00000002632 15243417773 0015307 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Aspects;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use libphonenumber\PhoneNumberType as libPhoneNumberType;
use ReflectionClass;
class PhoneNumberType
{
public static function all(): array
{
return (new ReflectionClass(libPhoneNumberType::class))->getConstants();
}
public static function isValid($type): bool
{
return ! is_null($type) && in_array($type, static::all(), true);
}
public static function isValidName($type): bool
{
return ! is_null($type) && in_array($type, array_keys(static::all()), true);
}
public static function getHumanReadableName($type): string|null
{
$name = array_search($type, static::all(), true);
return $name ? strtolower($name) : null;
}
public static function sanitize($types): int|array|null
{
$sanitized = Collection::make(is_array($types) ? $types : [$types])
->map(function ($format) {
// If the type equals a constant's name, return its value.
// Otherwise just return the value.
return Arr::get(static::all(), strtoupper($format), $format);
})
->filter(function ($format) {
return static::isValid($format);
})->unique();
return is_array($types) ? $sanitized->toArray() : $sanitized->first();
}
}
laravel-phone/src/Aspects/PhoneNumberCountry.php 0000644 00000001574 15243417773 0016035 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Aspects;
use Illuminate\Support\Collection;
use libphonenumber\PhoneNumberUtil;
class PhoneNumberCountry
{
public static function all(): array
{
return array_map('strtoupper', PhoneNumberUtil::getInstance()->getSupportedRegions());
}
public static function isValid($code): bool
{
return ! is_null($code) && in_array(strtoupper($code), static::all());
}
public static function sanitize($countries): string|array|null
{
$sanitized = Collection::make(is_array($countries) ? $countries : [$countries])
->filter(function ($value) {
return static::isValid($value);
})->map(function ($value) {
return strtoupper($value);
})->unique();
return is_array($countries) ? $sanitized->toArray() : $sanitized->first();
}
}
laravel-phone/src/Aspects/PhoneNumberFormat.php 0000644 00000002676 15243417773 0015626 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Aspects;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use libphonenumber\PhoneNumberFormat as libPhoneNumberFormat;
use ReflectionClass;
class PhoneNumberFormat
{
public static function all(): array
{
return (new ReflectionClass(libPhoneNumberFormat::class))->getConstants();
}
public static function isValid($format): bool
{
return ! is_null($format) && in_array($format, static::all(), true);
}
public static function isValidName($format): bool
{
return ! is_null($format) && in_array($format, array_keys(static::all()), true);
}
public static function getHumanReadableName($format): string|null
{
$name = array_search($format, static::all(), true);
return $name ? strtolower($name) : null;
}
public static function sanitize($formats): int|array|null
{
$sanitized = Collection::make(is_array($formats) ? $formats : [$formats])
->map(function ($format) {
// If the format equals a constant's name, return its value.
// Otherwise just return the value.
return Arr::get(static::all(), strtoupper($format), $format);
})
->filter(function ($format) {
return static::isValid($format);
})->unique();
return is_array($formats) ? $sanitized->toArray() : $sanitized->first();
}
}
laravel-phone/src/Exceptions/CountryCodeException.php 0000644 00000000347 15243417773 0017060 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Exceptions;
class CountryCodeException extends \Exception
{
public static function invalid($country)
{
return new static('Invalid country code "'.$country.'".');
}
}
laravel-phone/src/Exceptions/NumberParseException.php 0000644 00000002257 15243417773 0017047 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Exceptions;
use Illuminate\Support\Str;
use libphonenumber\NumberParseException as libNumberParseException;
class NumberParseException extends libNumberParseException
{
protected $number;
protected array $countries = [];
public static function countryRequired($number)
{
$exception = new static(
libNumberParseException::INVALID_COUNTRY_CODE,
'Number requires a country to be specified.'
);
$exception->number = $number;
return $exception;
}
public static function countryMismatch($number, $countries)
{
$countries = array_filter(is_array($countries) ? $countries : [$countries]);
$exception = new static(
libNumberParseException::INVALID_COUNTRY_CODE,
'Number does not match the provided '.Str::plural('country', count($countries)).'.'
);
$exception->number = $number;
$exception->countries = $countries;
return $exception;
}
public function getNumber()
{
return $this->number;
}
public function getCountries()
{
return $this->countries;
}
}
laravel-phone/src/Exceptions/NumberFormatException.php 0000644 00000000347 15243417773 0017223 0 ustar 00 <?php
namespace Propaganistas\LaravelPhone\Exceptions;
class NumberFormatException extends \Exception
{
public static function invalid($format)
{
return new static('Invalid number format "'.$format.'".');
}
}
laravel-phone/composer.json 0000644 00000002472 15243417773 0012045 0 ustar 00 {
"name": "propaganistas/laravel-phone",
"description": "Adds phone number functionality to Laravel based on Google's libphonenumber API.",
"keywords": [
"laravel",
"libphonenumber",
"validation",
"phone"
],
"license": "MIT",
"authors": [
{
"name": "Propaganistas",
"email": "Propaganistas@users.noreply.github.com"
}
],
"require": {
"php": "^8.0",
"illuminate/contracts": "^9.0|^10.0",
"illuminate/support": "^9.0|^10.0",
"illuminate/validation": "^9.0|^10.0",
"giggsey/libphonenumber-for-php": "^7.0|^8.0"
},
"require-dev": {
"orchestra/testbench": "*",
"phpunit/phpunit": "^9.5.10",
"nunomaduro/larastan": "^2.4",
"laravel/pint": "^1.4"
},
"autoload": {
"psr-4": {
"Propaganistas\\LaravelPhone\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Propaganistas\\LaravelPhone\\Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Propaganistas\\LaravelPhone\\PhoneServiceProvider"
]
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
laravel-phone/README.md 0000644 00000027626 15243417773 0010612 0 ustar 00 # Laravel Phone

[](https://packagist.org/packages/propaganistas/laravel-phone)
[](https://packagist.org/packages/propaganistas/laravel-phone)
[](https://packagist.org/packages/propaganistas/laravel-phone)
Adds phone number functionality to Laravel based on the [PHP port](https://github.com/giggsey/libphonenumber-for-php) of [libphonenumber by Google](https://github.com/googlei18n/libphonenumber).
## Table of Contents
- [Demo](#demo)
- [Installation](#installation)
- [Validation](#validation)
- [Attribute casting](#attribute-casting)
- [Utility class](#utility-phonenumber-class)
- [Formatting](#formatting)
- [Number information](#number-information)
- [Equality comparison](#equality-comparison)
- [Helper function](#helper-function)
- [Database considerations](#database-considerations)
## Demo
Check out the behavior of this package in the [demo](https://laravel-phone.herokuapp.com).
## Installation
Run the following command to install the latest applicable version of the package:
```bash
composer require propaganistas/laravel-phone
```
The Service Provider gets discovered automatically by Laravel.
In your languages directory, add an extra translation in every `validation.php` language file:
```php
'phone' => 'The :attribute field contains an invalid number.',
```
## Validation
Use the `phone` keyword in your validation rules array or use the `Propaganistas\LaravelPhone\Rules\Phone` rule class to define the rule in an expressive way.
To put constraints on the allowed originating countries, you can explicitly specify the allowed country codes.
```php
'phonefield' => 'phone:US,BE',
// 'phonefield' => (new Phone)->country(['US', 'BE'])
```
Or to make things more dynamic, you can also match against another data field holding a country code. For example, to require a phone number to match the provided country of residence.
Make sure the country field has the same name as the phone field but with `_country` appended for automatic discovery, or provide your custom country field name as a parameter to the validator:
```php
'phonefield' => 'phone',
// 'phonefield' => (new Phone)
'phonefield_country' => 'required_with:phonefield',
```
```php
'phonefield' => 'phone:custom_country_field',
// 'phonefield' => (new Phone)->countryField('custom_country_field')
'custom_country_field' => 'required_with:phonefield',
```
Note: country codes should be [*ISO 3166-1 alpha-2 compliant*](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements).
To support _any valid internationally formatted_ phone number next to the whitelisted countries, use the `INTERNATIONAL` parameter. This can be useful when you're expecting locally formatted numbers from a specific country but also want to accept any other foreign number entered properly:
```php
'phonefield' => 'phone:INTERNATIONAL,BE',
// 'phonefield' => (new Phone)->international()->country('BE')
```
To specify constraints on the number type, just append the allowed types to the end of the parameters, e.g.:
```php
'phonefield' => 'phone:mobile',
// 'phonefield' => (new Phone)->type('mobile')
```
The most common types are `mobile` and `fixed_line`, but feel free to use any of the types defined [here](https://github.com/giggsey/libphonenumber-for-php/blob/master/src/PhoneNumberType.php).
You can also enable lenient validation by using the `LENIENT` parameter.
With leniency enabled, only the length of the number is checked instead of actual carrier patterns.
```php
'phonefield' => 'phone:LENIENT',
// 'phonefield' => (new Phone)->lenient()
```
## Attribute casting
Two cast classes are provided for automatic casting of Eloquent model attributes:
```php
use Illuminate\Database\Eloquent\Model;
use Propaganistas\LaravelPhone\Casts\RawPhoneNumberCast;
use Propaganistas\LaravelPhone\Casts\E164PhoneNumberCast;
class User extends Model
{
public $casts = [
'phone_1' => RawPhoneNumberCast::class.':BE',
'phone_2' => E164PhoneNumberCast::class.':BE',
];
}
```
Both classes automatically cast the database value to a PhoneNumber object for further use in your application.
```php
$user->phone // PhoneNumber object or null
```
When setting a value, they both accept a string value or a PhoneNumber object.
The `RawPhoneNumberCast` mutates the database value to the raw input number, while the `E164PhoneNumberCast` writes a formatted E.164 phone number to the database.
In case of `RawPhoneNumberCast`, the cast needs to be hinted about the phone country in order to properly parse the raw number into a phone object.
In case of `E164PhoneNumberCast` and the value to be set is not already in some international format, the cast needs to be hinted about the phone country in order to properly mutate the value.
Both classes accept cast parameters in the same way:
1. When a similar named attribute exists, but suffixed with `_country` (e.g. phone_country), the cast will detect and use it automatically.
2. Provide another attribute's name as a cast parameter
3. Provide one or several country codes as cast parameters
```php
public $casts = [
'phone_1' => RawPhoneNumberCast::class.':country_field',
'phone_2' => E164PhoneNumberCast::class.':BE',
];
```
In order to not encounter any unexpected issues when using these casts, please always validate any input using the [validation](#validation) rules previously described.
#### ⚠️ Attribute assignment and `E164PhoneNumberCast`
Due to the nature of `E164PhoneNumberCast` a valid country attribute is expected if the number is not passed in international format. Since casts are applied in the order of the given values, be sure to set the country attribute _before_ setting the phone number attribute. Otherwise `E164PhoneNumberCast` will encounter an empty country value and throw an unexpected exception.
```php
// Wrong
$model->fill([
'phone' => '012 34 56 78',
'phone_country' => 'BE',
]);
// Correct
$model->fill([
'phone_country' => 'BE',
'phone' => '012 34 56 78',
]);
// Wrong
$model->phone = '012 34 56 78';
$model->phone_country = 'BE';
// Correct
$model->phone_country = 'BE';
$model->phone = '012 34 56 78';
```
## Utility PhoneNumber class
A phone number can be wrapped in the `Propaganistas\LaravelPhone\PhoneNumber` class to enhance it with useful utility methods. It's safe to directly reference these objects in views or when saving to the database as they will degrade gracefully to the E.164 format.
```php
use Propaganistas\LaravelPhone\PhoneNumber;
(string) new PhoneNumber('+3212/34.56.78'); // +3212345678
(string) new PhoneNumber('012 34 56 78', 'BE'); // +3212345678
```
### Formatting
A PhoneNumber can be formatted in various ways:
```php
$phone = new PhoneNumber('012/34.56.78', 'BE');
$phone->format($format); // See libphonenumber\PhoneNumberFormat
$phone->formatE164(); // +3212345678
$phone->formatInternational(); // +32 12 34 56 78
$phone->formatRFC3966(); // +32-12-34-56-78
$phone->formatNational(); // 012 34 56 78
// Formats so the number can be called straight from the provided country.
$phone->formatForCountry('BE'); // 012 34 56 78
$phone->formatForCountry('NL'); // 00 32 12 34 56 78
$phone->formatForCountry('US'); // 011 32 12 34 56 78
// Formats so the number can be clicked on and called straight from the provided country using a cellphone.
$phone->formatForMobileDialingInCountry('BE'); // 012345678
$phone->formatForMobileDialingInCountry('NL'); // +3212345678
$phone->formatForMobileDialingInCountry('US'); // +3212345678
```
### Number information
Get some information about the phone number:
```php
$phone = new PhoneNumber('012 34 56 78', 'BE');
$phone->getType(); // 'fixed_line'
$phone->isOfType('fixed_line'); // true
$phone->getCountry(); // 'BE'
$phone->isOfCountry('BE'); // true
```
### Equality comparison
Check if a given phone number is (not) equal to another one:
```php
$phone = new PhoneNumber('012 34 56 78', 'BE');
$phone->equals('012/34.56.76', 'BE') // true
$phone->equals('+32 12 34 56 78') // true
$phone->equals( $anotherPhoneObject ) // true/false
$phone->notEquals('045 67 89 10', 'BE') // true
$phone->notEquals('+32 45 67 89 10') // true
$phone->notEquals( $anotherPhoneObject ) // true/false
```
### Helper function
The package exposes the `phone()` helper function that returns a `Propaganistas\LaravelPhone\PhoneNumber` instance or the formatted string if `$format` was provided:
```php
phone($number, $country = [], $format = null)
```
## Database considerations
> Disclaimer: Phone number handling is quite different in each application. The topics mentioned below are therefore meant as a set of thought starters; support will **not** be provided.
Storing phone numbers in a database has always been a speculative topic and there's simply no silver bullet. It all depends on your application's requirements. Here are some things to take into account, along with an implementation suggestion. Your ideal database setup will probably be a combination of some of the pointers detailed below.
### Uniqueness
The E.164 format globally and uniquely identifies a phone number across the world. It also inherently implies a specific country and can be supplied as-is to the `phone()` helper.
You'll need:
* One column to store the phone number
* To format the phone number to E.164 before persisting it
Example:
* User input = `012/45.65.78`
* Database column
* `phone` (varchar) = `+3212456578`
### Presenting the phone number the way it was inputted
If you store formatted phone numbers the raw user input will unretrievably get lost. It may be beneficial to present your users with their very own inputted phone number, for example in terms of improved user experience.
You'll need:
* Two columns to store the raw input and the correlated country
Example:
* User input = `012/34.56.78`
* Database columns
* `phone` (varchar) = `012/34.56.78`
* `phone_country` (varchar) = `BE`
### Supporting searches
Searching through phone numbers can quickly become ridiculously complex and will always require deep understanding of the context and extent of your application. Here's _a_ possible approach covering quite a lot of "natural" use cases.
You'll need:
* Three additional columns to store searchable variants of the phone number:
* Normalized input (raw input with all non-alpha characters stripped)
* National formatted phone number (with all non-alpha characters stripped)
* E.164 formatted phone number
* Probably a `saving()` observer (or equivalent) to prefill the variants before persistence
* An extensive search query utilizing the searchable variants
Example:
* User input = `12/34.56.78`
* Observer method:
```php
public function saving(User $user)
{
if ($user->isDirty('phone') && $user->phone) {
$user->phone_normalized = preg_replace('[^0-9]', '', $user->phone);
$user->phone_national = preg_replace('[^0-9]', '', phone($user->phone, $user->phone_country)->formatNational());
$user->phone_e164 = phone($user->phone, $user->phone_country)->formatE164();
}
}
```
* Database columns
* `phone_normalized` (varchar) = `12345678`
* `phone_national` (varchar) = `012345678`
* `phone_e164` (varchar) = `+3212345678`
* Search query:
```php
// $search holds the search term
User::where(function($query) use ($search) {
$query->where('phone_normalized', 'LIKE', preg_replace('[^0-9]', '', $search) . '%')
->orWhere('phone_national', 'LIKE', preg_replace('[^0-9]', '', $search) . '%')
->orWhere('phone_e164', 'LIKE', preg_replace('[^+0-9]', '', $search) . '%')
});
```