7.x 新特性之自定义 Cast 及使用场景
在以往的版本中,Laravel 提供了一些默认的类型转换
<?php
protected $casts = [
'is_admin' => 'boolean',
]
Laravel 7 引入了自定义 Cast,使得用户可以自定义 Cast。那么,如何在项目中使用自定义 Cast 呢?下面我将列举三个场景帮助大家理解。
示例 1 - 替代模型事件
对于一些数据库字段来说,我们希望能够对其进行加密后保存。
在以往的版本中,一种较好的解决方式就是对保存和读取事件进行监听,实现属性自动加密和解密。
app/Traits/Encryptable.php
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Crypt;
trait Encryptable
{
public static function bootEncryptable()
{
static::saving(function($model){
foreach ($model->encryptable as $attribute){
$model->$attribute = Crypt::encrypt($model->$attribute);
}
});
static::retrieved(function($model){
foreach ($model->encryptable as $attribute){
$model->$attribute = Crypt::decrypt($model->$attribute);
}
});
}
}
使用
App\User.php
<?php
use App\Traits\Encryptable;
class User
{
use Encryptable;
protected $encryptable = ['name'];
}
该方式可以用自定义 Cast 实现。首先创建自定义 Cast 类,命名建议以 Cast 结尾,该类需要实现 CastsAttributes
接口
app/Casts/EncryptCast.php
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Facades\Crypt;
class EncryptCast implements CastsAttributes
{
/**
* @inheritDoc
*/
public function get($model, string $key, $value, array $attributes)
{
return Crypt::decrypt($value);
}
/**
* @inheritDoc
*/
public function set($model, string $key, $value, array $attributes)
{
return Crypt::encrypt($value);
}
}
使用
App\User.php
<?php
class User
{
protected $casts = [
'name' => EncryptCast::class,
];
}
通过自定义 Cast,可以更直观的管理特性属性的转换。
示例 2 - 封装属性
对于一些基本的数据类型,比如 email
、 phone
等等,可以将其封装成类,这样可以做有很多好处
- 验证类型的正确性
- 更清晰的显示信息
- 绑定关联的行为
- …
我们以 Email 为例,可以创建 Email 类来表示
app/Email.php
<?php
namespace App;
use Illuminate\Support\Str;
use InvalidArgumentException;
class Email
{
public $address;
/**
* Email constructor.
*
* @param $address
*/
public function __construct($address)
{
if (! filter_var($address, FILTER_VALIDATE_EMAIL)){
throw new InvalidArgumentException("非法的邮件地址");
}
$this->address = $address;
}
/**
* @return string
*/
public function domain()
{
return Str::of($this->address)->after('@')->__toString();
}
public function __toString()
{
return $this->address;
}
}
将 email 封装成类,可以对 Email 类型进行验证,同时也可以提供一些相关的行为,比如获取邮件的域名。测试
<?php
$email = new Email('foo@bar.com');
$email->domain(); // bar.com
对于基本类型的封装,感兴趣的可以查看这篇文章 PHP 技巧 - 封装基本的数据类型
Laravel 的自定义 Cast 可以方便的集成用户的自定义类型。
App\Casts\EmailCast.php
<?php
namespace App\Casts;
use App\Email;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class EmailCast implements CastsAttributes
{
/**
* @inheritDoc
*/
public function get($model, string $key, $value, array $attributes)
{
return new Email($value);
}
/**
* @inheritDoc
*/
public function set($model, string $key, $value, array $attributes)
{
return $value->address;
}
}
使用
<?php
use App\Casts\EmailCast;
class User
{
protected $casts = [
'email' => EmailCast::class,
];
}
测试
<?php
$user = User::first();
$user->email = new Email('foo@bar.com'); // 传入类型实例可以确保数据的正确性
$user->save();
$user->email->domain(); // bar.com
$user->email->address; // foo@bar.com
(string) $user->email; // foo@bar.com
示例 3 - 同时管理多个属性
Vocation (假期) 类有三个属性:名称、开始时间、结束时间
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Vocation extends Model
{
protected $fillable = [
'name', 'start', 'end'
]
}
开始时间和结束时间构成了一个时间表,可以使用自定义 Cast 同时管理这两者。
创建基本类型
App\Schedule.php
<?php
namespace App;
use Carbon\Carbon;
class Schedule
{
public $start;
public $end;
/**
* Schedule constructor.
*
* @param $start
* @param $end
*/
public function __construct($start, $end)
{
$this->start = $start;
$this->end = $end;
}
/**
* 假期是否快结束
*
* @return bool
*/
public function isNearEnd() : bool
{
return Carbon::parse($this->end)->lte(Carbon::tomorrow());
}
}
创建对应的 Cast
App\Casts\ScheduleCast.php
<?php
namespace App\Casts;
use App\Schedule;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class ScheduleCast implements CastsAttributes
{
/**
* @inheritDoc
*/
public function get($model, string $key, $value, array $attributes)
{
return new Schedule(
$attributes['start'],
$attributes['end']
);
}
/**
* @inheritDoc
*/
public function set($model, string $key, $value, array $attributes)
{
return [
'start' => $value->start,
'end' => $value->end,
];
}
}
使用
<?php
namespace App;
use App\Casts\ScheduleCast;
use Illuminate\Database\Eloquent\Model;
class Vocation extends Model
{
protected $casts = [
'schedule' => ScheduleCast::class
];
}
运行
<?php
$vocation = Vocation::create([
'name' => '测试',
'start' => now(),
'end' => now()->addDay()
]);
$vocation->schedule->isNearEnd(); // true
$vocation->schedule->end = now()->addWeek();
$vocation->schedule->isNearEnd(); // false
$vocation->save();
总结
自定义 Cast 清晰地定义了类型转换的职责,减少了「胖模型」的复杂度,相信该特性会被广泛使用。