laravel-phone包实现电话号码验证、格式转换等功能

相信你的系统中也一定会有手机注册以及发送短信验证码的功能。那么验证用户填写的手机号是否规范合法就是我们完成后续功能的第一步。propaganistas/laravel-phone包为我们提供了非常强大且可靠的电话验证功能,并且适用于全球所有国家的号码验证。

安装

composer require propaganistas/laravel-phone

配置

服务提供者会被 Laravel 自动发现。在语言目录lang中,为每个 validation.php 语言文件添加一条额外的翻译:

'phone' => 'The :attribute field must be a valid number.',

在线工具网站

https://laravel-phone.herokuapp.com用于测试Laravel-Phone包中的电话号码验证组件。

验证请求字段

你可以在验证规则数组中使用phone关键字,或者使用 Propaganistas\LaravelPhone\Rules\Phone规则类来以更具表现力的方式定义规则。 要限制国家/地区,你可以明确指定允许的国家/地区的代码:

'my_input'       => 'phone:US,BE',
// 'my_input'    => (new Phone)->country(['US', 'BE'])

或者为了使功能更加灵活,还可以与另一个包含国家/地区代码的数据字段进行匹配。例如,要求用户的电话号码与用户提供的居住国家/地区匹配。请确保国家/地区字段的名称与电话号码字段的名称相同,但附加”_country”后缀以便laravel-phone包自动识别:

'my_input'            => 'phone',
// 'my_input'         => (new Phone)
'my_input_country'    => 'required_with:my_input',

或者将自定义的国家/地区字段名称作为参数传递给验证器:

'my_input'            => 'phone:custom_country_field',
// 'my_input'         => (new Phone)->countryField('custom_country_field')
'custom_country_field'  => 'required_with:my_input',

注意:国家代码应符合ISO 3166-1 alpha-2标准。

为了支持除白名单国家/地区之外的任何有效国际格式的电话号码,请使用INTERNATIONAL参数。当你预期接收来自特定国家/地区的本地格式号码,但同时也希望接受任何其他正确输入的国外号码时,此功能会非常有用:

'my_input'            => 'phone:INTERNATIONAL,BE',
// 'my_input'         => (new Phone)->international()->country('BE')

要指定电话号码类型的约束,请将允许的类型附加到参数后面,例如:

'my_input'       => 'phone:mobile',
// 'my_input'    => (new Phone)->type('mobile')
// 'my_input'    => (new Phone)->type(libphonenumber\PhoneNumberType::MOBILE)

最常见的类型是移动电话mobile和固定电话fixed_line,但你可以随意使用此处定义的任何电话号码类型。

在电话号码类型名称前加上感叹号即可将其列入黑名单。请注意,你不能同时使用白名单和黑名单:

'my_input'       => 'phone:!mobile',
// 'my_input'    => (new Phone)->notType('mobile')
// 'my_input'    => (new Phone)->notType(libphonenumber\PhoneNumberType::MOBILE)

你还可以使用LENIENT参数启用宽松验证。启用宽松验证后,系统只会检查数字的长度,而不会检查实际的运营商模式:

'my_input'       => 'phone:LENIENT',
// 'my_input'    => (new Phone)->lenient()

模型属性转换(cast)

为了方便对Eloquent模型属性进行自动类型转换,我们提供了两个转换类:

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',
    ];
}

这两个类都会自动将数据库中的值转换为PhoneNumber对象,以便在你的应用程序中进一步使用:

$user->phone // PhoneNumber object or null

在设置值时,它们都接受字符串值或PhoneNumber对象。RawPhoneNumberCast会将数据库值更改为原始输入号码,而E164PhoneNumberCast会将格式化的E.164电话号码写入数据库。

对于RawPhoneNumberCast类,需要指定电话号码所属国家/地区,才能正确地将原始号码解析为PhoneNumber对象。对于E164PhoneNumberCast类,如果待设置的值并非已采用某种国际格式,则也需要指定电话号码所属国家/地区,才能正确地转换该值。

这两个类以相同方式接受类型转换参数:

  • 如果存在名称相似但后缀为”_country”的属性(例如phone_country),则类型转换器会自动检测并使用该属性
  • 提供另一个属性的名称作为类型转换参数
  • 提供一个或多个国家/地区代码作为类型转换参数
public $casts = [
    'phone_1' => RawPhoneNumberCast::class.':country_field',
    'phone_2' => E164PhoneNumberCast::class.':BE',
];

重要提示:这两种类型转换都需要有效的电话号码才能顺利地进行PhoneNumber对象的转换。请在将电话号码设置到模型之前进行验证。有关如何验证电话号码,请参阅上节“验证请求字段”。

⚠️ 属性赋值和E164PhoneNumberCast

由于E164PhoneNumberCast的特性,如果电话号码不是国际格式,则需要提供有效的国家/地区代码。由于Laravel在设置属性时会立即执行类型转换,因此请务必在设置电话号码属性之前设置国家/地区代码属性。否则,E164PhoneNumberCast将遇到空的国家/地区代码值并抛出意外异常。

// 错误
$model->fill([
    'phone' => '012 34 56 78',
    'phone_country' => 'BE',
]);

// 正确
$model->fill([
    'phone_country' => 'BE',
    'phone' => '012 34 56 78',
]);

// 错误
$model->phone = '012 34 56 78';
$model->phone_country = 'BE';

// 正确
$model->phone_country = 'BE';
$model->phone = '012 34 56 78';

实用电话号码类PhoneNumber

电话号码可以封装在Propaganistas\LaravelPhone\PhoneNumber类中,从而为其添加实用的方法。在视图中或保存到数据库时,可以直接引用这些对象,它们会优雅地降级为E.164格式。

use Propaganistas\LaravelPhone\PhoneNumber;

(string) new PhoneNumber('+3212/34.56.78');                // +3212345678
(string) new PhoneNumber('012 34 56 78', 'BE');            // +3212345678

或者,你可以使用phone()辅助函数。它会返回一个Propaganistas\LaravelPhone\PhoneNumber实例,如果提供了$format参数,则返回格式化后的字符串:

phone('+3212/34.56.78');                // PhoneNumber instance
phone('012 34 56 78', 'BE');            // PhoneNumber instance
phone('012 34 56 78', 'BE', $format);   // string

格式化号码

可以多种方式格式化PhoneNumber实例:

$phone = new PhoneNumber('012/34.56.78', 'BE');

$phone->format($format);       // 见libphonenumber\PhoneNumberFormat
$phone->formatE164();          // +3212345678
$phone->formatInternational(); // +32 12 34 56 78
$phone->formatRFC3966();       // tel:+32-12-34-56-78
$phone->formatNational();      // 012 34 56 78

// 格式化后,即可直接从提供的国家/地区拨打该号码
$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

// 格式化后的号码在手机上可以直接点击,然后可以用手机直接从指定的国家/地区拨打该号码
$phone->formatForMobileDialingInCountry('BE'); // 012345678
$phone->formatForMobileDialingInCountry('NL'); // +3212345678
$phone->formatForMobileDialingInCountry('US'); // +3212345678

号码信息

获取一些关于该号码的信息:

$phone = new PhoneNumber('012 34 56 78', 'BE');

$phone->getType();              // libphonenumber\PhoneNumberType::FIXED_LINE
$phone->isOfType('fixed_line'); // true    (or use $phone->isOfType(libphonenumber\PhoneNumberType::FIXED_LINE) )
$phone->getCountry();           // 'BE'
$phone->isOfCountry('BE');      // true

等值比较

比较两个号码相同还是不同:

$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

数据库注意事项

免责声明:不同应用程序处理电话号码的方式各不相同。因此,以下内容仅供参考,旨在启发思考;我们不提供相关技术支持。

将电话号码存储在数据库中一直是一个值得探讨的问题,而且并没有一劳永逸的解决方案(没有银弹)。一切都取决于你的应用程序需求。以下是一些需要考虑的因素,以及一个实现建议。你理想的数据库设置可能需要结合以下列出的一些要点。