访问器(Accessor)、**修改器(Mutator)和属性类型转换(Cast)**用于在读取或设置 Eloquent 模型属性时对值进行转换。
- 访问器 — 对从数据库读取的原始值进行加工,再返回给应用
- 修改器 — 对应用中设置的值进行加工,再保存到数据库
- 类型转换 — 无需额外方法,通过声明式配置完成属性类型的转换
定义访问器
要定义访问器,需要在模型中添加一个 protected 方法。方法名使用驼峰式,返回类型为 Illuminate\Database\Eloquent\Casts\Attribute。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取用户的名字(first name)。
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
}
get 闭包会收到数据库中的原始值。通过模型实例可以以 first_name 属性的形式访问。
$user = User::find(1);
$firstName = $user->first_name; // 已经应用了 ucfirst()
如果希望访问器计算得到的值也出现在 JSON/数组中,请把它以蛇形命名的形式加入模型的 $appends 属性。
用多个属性生成值对象
get 闭包的第二个参数是 $attributes(模型的所有属性)。可以组合多列返回一个值对象。
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
);
}
访问器的缓存
返回值对象的访问器,Eloquent 会自动缓存同一实例。如果希望字符串、数字等基本类型也被缓存,可以调用 shouldCache()。
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}
如果要关闭对象缓存,可以使用 withoutObjectCaching()。
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
)->withoutObjectCaching();
}
定义修改器
修改器通过 Attribute::make() 的 set 参数来定义。可以与访问器写在同一个方法中。
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
当给模型属性赋值时,set 闭包会被调用。
$user = User::find(1);
$user->first_name = 'SALLY'; // 会经过 strtolower() 保存为 'sally'
同时写入多个属性
set 闭包返回数组时,可以一次性更新多个列。
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
set: fn (Address $value) => [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
],
);
}
属性类型转换
类型转换允许你在不写访问器和修改器的情况下声明属性的类型转换,是一种简洁的方式。在模型的 casts() 方法中返回一个数组。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected function casts(): array
{
return [
'is_admin' => 'boolean',
'score' => 'float',
'settings' => 'array',
'created_at' => 'datetime',
];
}
}
内置类型转换一览
| 类型转换 | 说明 |
|---|
integer / int | 整数 |
float / double / real | 浮点数 |
decimal:<小数位数> | 指定小数位数的小数 |
string | 字符串 |
boolean / bool | 布尔值(包含 0/1) |
array | JSON 与数组互转 |
object | JSON 与对象互转 |
collection | JSON 转换为 Collection |
date | 日期(Carbon) |
datetime | 日期时间(Carbon) |
immutable_date | 不可变日期 |
immutable_datetime | 不可变日期时间 |
timestamp | UNIX 时间戳 |
hashed | 保存时哈希 |
encrypted | 保存时加密 |
null 属性不会被转换。此外,请不要为与关联同名的属性或主键设置类型转换。
Stringable 类型转换
使用 AsStringable 可以让属性以 Illuminate\Support\Stringable 对象的形式使用。
use Illuminate\Database\Eloquent\Casts\AsStringable;
protected function casts(): array
{
return [
'bio' => AsStringable::class,
];
}
数组 / JSON 转换
可以将 JSON/TEXT 列透明地作为 PHP 数组使用。
protected function casts(): array
{
return [
'options' => 'array',
];
}
$user = User::find(1);
// 自动作为 PHP 数组返回
$options = $user->options;
// 修改后保存会自动被 JSON 序列化
$user->options = array_merge($options, ['theme' => 'dark']);
$user->save();
也可以使用 -> 运算符只更新 JSON 中的某个键。
$user->update(['options->theme' => 'dark']);
AsArrayObject / AsCollection 类型转换
标准的 array 转换在直接修改数组中的某个偏移时会报错。使用 AsArrayObject 或 AsCollection 可以规避此问题。
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;
protected function casts(): array
{
return [
'options' => AsArrayObject::class, // 以 ArrayObject 形式使用
'tags' => AsCollection::class, // 以 Collection 形式使用
];
}
如果需要使用自定义 Collection 类,可以通过 using() 指定。
use App\Collections\TagCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
protected function casts(): array
{
return [
'tags' => AsCollection::using(TagCollection::class),
];
}
日期时间类型转换
created_at / updated_at 默认会被转换为 Carbon 实例。其他日期时间列也可以用同样方式定义。
protected function casts(): array
{
return [
'published_at' => 'datetime',
'expires_at' => 'immutable_datetime',
];
}
指定格式后,JSON 序列化时会使用该格式。
protected function casts(): array
{
return [
'published_at' => 'datetime:Y-m-d',
];
}
如果要修改所有日期的默认序列化格式,可以重写 serializeDate()(不影响数据库中的存储格式)。
use DateTimeInterface;
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format('Y-m-d');
}
使用 immutable_datetime 时会返回 CarbonImmutable 而不是 Carbon。它可以在不修改原实例的情况下进行日期时间运算,便于写出无副作用的代码。
Enum 类型转换
PHP 8.1 及以上版本的 Backed Enum 也可以作为类型转换。
<?php
namespace App\Enums;
enum ServerStatus: string
{
case Provisioned = 'provisioned';
case Ready = 'ready';
case Archived = 'archived';
}
use App\Enums\ServerStatus;
protected function casts(): array
{
return [
'status' => ServerStatus::class,
];
}
数据库中存储的是 Enum 的 backing 值(string 或 int),读取时会被转换成 Enum 实例。
$server = Server::find(1);
if ($server->status === ServerStatus::Provisioned) {
$server->status = ServerStatus::Ready;
$server->save();
}
Enum 的数组转换
如果需要在一列中以数组的形式保存多个 Enum 值,可以使用 AsEnumCollection。
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
protected function casts(): array
{
return [
'statuses' => AsEnumCollection::of(ServerStatus::class),
];
}
查询时的类型转换
在执行查询时动态添加类型转换,可以使用 withCasts()。
use App\Models\Post;
use App\Models\User;
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id'),
])->withCasts([
'last_posted_at' => 'datetime',
])->get();
自定义类型转换
你也可以创建自己的类型转换类。实现 CastsAttributes 接口,并定义 get 与 set 方法。
php artisan make:cast AsJson
关于详细的实现方式(Value Object 模式、单向转换、Castables 等),请参考下面的高级页面。
自定义类型转换详解
介绍 CastsAttributes 接口的实现方式,以及 Value Object 模式、Castables 等高级自定义类型转换。
相关页面
Eloquent API 资源
查看如何使用资源类将模型转换为一致的 JSON API 响应。