Eloquent: 修改器与类型转换
简介
访问器、修改器和属性转换允许你在模型实例中检索或设置 Eloquent 属性值时对其进行转换。例如,你可能希望在将值存储到数据库时使用 Laravel 加密器 对其进行加密,然后在 Eloquent 模型中访问该属性时自动解密。或者,你可能希望将存储在数据库中的 JSON 字符串在通过 Eloquent 模型访问时转换为数组。
访问器与修改器
定义访问器
访问器会在访问 Eloquent 属性值时对其进行转换。要定义访问器,请在模型上创建一个受保护的方法来表示可访问的属性。如果适用,此方法名称应对应于底层模型属性 / 数据库列的“驼峰式”表示形式。
在本例中,我们将为 first_name 属性定义一个访问器。当尝试检索 first_name 属性的值时,Eloquent 会自动调用该访问器。所有属性访问器 / 修改器方法都必须声明 Illuminate\Database\Eloquent\Casts\Attribute 的返回类型提示。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\Attribute; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the user's first name.12 */13 protected function firstName(): Attribute14 {15 return Attribute::make(16 get: fn (string $value) => ucfirst($value),17 );18 }19}
所有访问器方法都返回一个 Attribute 实例,该实例定义了如何访问以及(可选)如何修改该属性。在本例中,我们仅定义了如何访问该属性。为此,我们向 Attribute 类构造函数提供 get 参数。
如你所见,列的原始值会被传递给访问器,允许你操作并返回该值。要访问访问器的值,只需在模型实例上访问 first_name 属性即可。
1use App\Models\User;2 3$user = User::find(1);4 5$firstName = $user->first_name;
如果你希望这些计算出的值添加到模型的数组 / JSON 表示中,你需要将它们追加进去。
从多个属性构建值对象
有时你的访问器可能需要将多个模型属性转换为单个“值对象”。为此,你的 get 闭包可以接受第二个参数 $attributes,该参数将自动提供给闭包,并包含模型当前所有属性的数组。
1use App\Support\Address; 2use Illuminate\Database\Eloquent\Casts\Attribute; 3 4/** 5 * Interact with the user's address. 6 */ 7protected function address(): Attribute 8{ 9 return Attribute::make(10 get: fn (mixed $value, array $attributes) => new Address(11 $attributes['address_line_one'],12 $attributes['address_line_two'],13 ),14 );15}
访问器缓存
当从访问器返回值对象时,对值对象所做的任何更改都会在保存模型之前自动同步回模型。这是可能的,因为 Eloquent 会保留访问器返回的实例,以便每次调用访问器时都能返回同一个实例。
1use App\Models\User;2 3$user = User::find(1);4 5$user->address->lineOne = 'Updated Address Line 1 Value';6$user->address->lineTwo = 'Updated Address Line 2 Value';7 8$user->save();
然而,有时你可能希望为字符串和布尔值等原始数据类型启用缓存,特别是当它们计算开销很大时。要实现这一点,你可以在定义访问器时调用 shouldCache 方法。
1protected function hash(): Attribute2{3 return Attribute::make(4 get: fn (string $value) => bcrypt(gzuncompress($value)),5 )->shouldCache();6}
如果你想禁用属性的对象缓存行为,可以在定义属性时调用 withoutObjectCaching 方法。
1/** 2 * Interact with the user's address. 3 */ 4protected function address(): Attribute 5{ 6 return Attribute::make( 7 get: fn (mixed $value, array $attributes) => new Address( 8 $attributes['address_line_one'], 9 $attributes['address_line_two'],10 ),11 )->withoutObjectCaching();12}
定义修改器
修改器会在设置 Eloquent 属性值时对其进行转换。要定义修改器,你可以在定义属性时提供 set 参数。让我们为 first_name 属性定义一个修改器。当我们尝试在模型上设置 first_name 属性的值时,该修改器将自动被调用。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\Attribute; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Interact with the user's first name.12 */13 protected function firstName(): Attribute14 {15 return Attribute::make(16 get: fn (string $value) => ucfirst($value),17 set: fn (string $value) => strtolower($value),18 );19 }20}
修改器闭包将接收正在设置的属性值,允许你操作该值并返回转换后的值。要使用我们的修改器,只需在 Eloquent 模型上设置 first_name 属性即可。
1use App\Models\User;2 3$user = User::find(1);4 5$user->first_name = 'Sally';
在此示例中,set 回调将以值 Sally 被调用。然后,修改器将对该名称应用 strtolower 函数,并将结果值设置到模型内部的 $attributes 数组中。
修改多个属性
有时你的修改器可能需要设置底层模型上的多个属性。为此,你可以从 set 闭包返回一个数组。数组中的每个键都应对应与模型关联的底层属性 / 数据库列。
1use App\Support\Address; 2use Illuminate\Database\Eloquent\Casts\Attribute; 3 4/** 5 * Interact with the user's address. 6 */ 7protected function address(): Attribute 8{ 9 return Attribute::make(10 get: fn (mixed $value, array $attributes) => new Address(11 $attributes['address_line_one'],12 $attributes['address_line_two'],13 ),14 set: fn (Address $value) => [15 'address_line_one' => $value->lineOne,16 'address_line_two' => $value->lineTwo,17 ],18 );19}
属性转换
属性转换提供了一种类似于访问器和修改器的功能,而无需在模型上定义任何额外的方法。相反,模型的 casts 方法提供了一种将属性转换为通用数据类型的便捷方式。
casts 方法应返回一个数组,其中键是要转换的属性名称,值是你希望将列转换成的类型。支持的转换类型有:
arrayAsFluent::classAsStringable::classAsUri::classbooleancollectiondatedatetimeimmutable_dateimmutable_datetimedecimal:<precision>doubleencryptedencrypted:arrayencrypted:collectionencrypted:objectfloathashedintegerobjectrealstringtimestamp
为了演示属性转换,让我们将存储在数据库中为整数(0 或 1)的 is_admin 属性转换为布尔值。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Model; 6 7class User extends Model 8{ 9 /**10 * Get the attributes that should be cast.11 *12 * @return array<string, string>13 */14 protected function casts(): array15 {16 return [17 'is_admin' => 'boolean',18 ];19 }20}
定义转换后,当你访问 is_admin 属性时,它将始终被转换为布尔值,即使底层值在数据库中存储为整数。
1$user = App\Models\User::find(1);2 3if ($user->is_admin) {4 // ...5}
如果你需要在运行时添加新的临时转换,可以使用 mergeCasts 方法。这些转换定义将添加到模型已定义的任何转换中。
1$user->mergeCasts([2 'is_admin' => 'integer',3 'options' => 'object',4]);
值为 null 的属性不会被转换。此外,你不应定义与关系同名的转换(或属性),也不应为模型的主键分配转换。
字符串转换 (Stringable Casting)
你可以使用 Illuminate\Database\Eloquent\Casts\AsStringable 转换类将模型属性转换为 流畅的 Illuminate\Support\Stringable 对象。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\AsStringable; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the attributes that should be cast.12 *13 * @return array<string, string>14 */15 protected function casts(): array16 {17 return [18 'directory' => AsStringable::class,19 ];20 }21}
数组与 JSON 转换
array 转换在使用序列化为 JSON 的列时特别有用。例如,如果你的数据库有包含序列化 JSON 的 JSON 或 TEXT 字段类型,在该属性上添加 array 转换将在你于 Eloquent 模型中访问它时自动将其反序列化为 PHP 数组。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Model; 6 7class User extends Model 8{ 9 /**10 * Get the attributes that should be cast.11 *12 * @return array<string, string>13 */14 protected function casts(): array15 {16 return [17 'options' => 'array',18 ];19 }20}
定义转换后,你可以访问 options 属性,它将自动从 JSON 反序列化为 PHP 数组。当你设置 options 属性的值时,给定的数组将自动序列化回 JSON 以进行存储。
1use App\Models\User; 2 3$user = User::find(1); 4 5$options = $user->options; 6 7$options['key'] = 'value'; 8 9$user->options = $options;10 11$user->save();
若要以更简洁的语法更新 JSON 属性的单个字段,你可以 将该属性设为可批量赋值,并在调用 update 方法时使用 -> 运算符。
1$user = User::find(1);2 3$user->update(['options->key' => 'value']);
JSON 与 Unicode
如果你想以 JSON 形式存储包含未转义 Unicode 字符的数组属性,可以使用 json:unicode 转换。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'options' => 'json:unicode',10 ];11}
数组对象与集合转换
虽然标准的 array 转换足以满足许多应用程序的需求,但它确实有一些缺点。由于 array 转换返回的是原始类型,因此无法直接修改数组的偏移量。例如,以下代码将触发 PHP 错误:
1$user = User::find(1);2 3$user->options['key'] = $value;
为了解决这个问题,Laravel 提供了 AsArrayObject 转换,它将你的 JSON 属性转换为 ArrayObject 类。此功能通过 Laravel 的 自定义转换 实现,允许 Laravel 智能地缓存和转换被修改的对象,从而可以修改单个偏移量而不会触发 PHP 错误。要使用 AsArrayObject 转换,只需将其分配给属性即可。
1use Illuminate\Database\Eloquent\Casts\AsArrayObject; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'options' => AsArrayObject::class,12 ];13}
同样,Laravel 提供了 AsCollection 转换,它将你的 JSON 属性转换为 Laravel 集合 (Collection) 实例。
1use Illuminate\Database\Eloquent\Casts\AsCollection; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'options' => AsCollection::class,12 ];13}
如果你希望 AsCollection 转换实例化自定义集合类而不是 Laravel 的基础集合类,你可以将集合类名称作为转换参数提供。
1use App\Collections\OptionCollection; 2use Illuminate\Database\Eloquent\Casts\AsCollection; 3 4/** 5 * Get the attributes that should be cast. 6 * 7 * @return array<string, string> 8 */ 9protected function casts(): array10{11 return [12 'options' => AsCollection::using(OptionCollection::class),13 ];14}
of 方法可用于指示集合项应通过集合的 mapInto 方法 映射到给定的类中。
1use App\ValueObjects\Option; 2use Illuminate\Database\Eloquent\Casts\AsCollection; 3 4/** 5 * Get the attributes that should be cast. 6 * 7 * @return array<string, string> 8 */ 9protected function casts(): array10{11 return [12 'options' => AsCollection::of(Option::class)13 ];14}
将集合映射到对象时,对象应实现 Illuminate\Contracts\Support\Arrayable 和 JsonSerializable 接口,以定义其实例应如何序列化为 JSON 存储到数据库中。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Support\Arrayable; 6use JsonSerializable; 7 8class Option implements Arrayable, JsonSerializable 9{10 public string $name;11 public mixed $value;12 public bool $isLocked;13 14 /**15 * Create a new Option instance.16 */17 public function __construct(array $data)18 {19 $this->name = $data['name'];20 $this->value = $data['value'];21 $this->isLocked = $data['is_locked'];22 }23 24 /**25 * Get the instance as an array.26 *27 * @return array{name: string, data: string, is_locked: bool}28 */29 public function toArray(): array30 {31 return [32 'name' => $this->name,33 'value' => $this->value,34 'is_locked' => $this->isLocked,35 ];36 }37 38 /**39 * Specify the data which should be serialized to JSON.40 *41 * @return array{name: string, data: string, is_locked: bool}42 */43 public function jsonSerialize(): array44 {45 return $this->toArray();46 }47}
二进制转换
如果你的 Eloquent 模型除了自动递增 ID 列外,还有 二进制类型 的 uuid 或 ulid 列,你可以使用 AsBinary 转换自动将其值转换为二进制表示形式,反之亦然。
1use Illuminate\Database\Eloquent\Casts\AsBinary; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'uuid' => AsBinary::uuid(),12 'ulid' => AsBinary::ulid(),13 ];14}
在模型上定义转换后,你可以将 UUID / ULID 属性值设置为对象实例或字符串。Eloquent 将自动把该值转换为其二进制表示形式。检索属性值时,你将始终获得一个纯文本字符串值。
1use Illuminate\Support\Str;2 3$user->uuid = Str::uuid();4 5return $user->uuid;6 7// "6e8cdeed-2f32-40bd-b109-1e4405be2140"
日期转换
默认情况下,Eloquent 会将 created_at 和 updated_at 列转换为 Carbon 实例,它扩展了 PHP 的 DateTime 类并提供了各种有用的方法。你可以通过在模型的 casts 方法中定义额外的日期转换来转换其他日期属性。通常,日期应使用 datetime 或 immutable_datetime 转换类型。
定义 date 或 datetime 转换时,你还可以指定日期的格式。当 模型被序列化为数组或 JSON 时,将使用此格式。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'created_at' => 'datetime:Y-m-d',10 ];11}
当一列被转换为日期时,你可以将相应的模型属性值设置为 UNIX 时间戳、日期字符串 (Y-m-d)、日期时间字符串或 DateTime / Carbon 实例。日期的值将被正确转换并存储在数据库中。
你可以通过在模型上定义 serializeDate 方法来自定义模型所有日期的默认序列化格式。此方法不影响日期在数据库中存储的格式。
1/**2 * Prepare a date for array / JSON serialization.3 */4protected function serializeDate(DateTimeInterface $date): string5{6 return $date->format('Y-m-d');7}
要指定在数据库中实际存储模型日期时应使用的格式,你应该在模型的 Table 属性上使用 dateFormat 参数。
1use Illuminate\Database\Eloquent\Attributes\Table;2 3#[Table(dateFormat: 'U')]4class Flight extends Model5{6 // ...7}
日期转换、序列化与时区
默认情况下,date 和 datetime 转换会将日期序列化为 UTC ISO-8601 日期字符串 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ),无论在应用程序的 timezone 配置选项中指定了什么时区。强烈建议始终使用此序列化格式,并通过不更改应用程序 timezone 配置选项的默认 UTC 值,将应用程序的日期存储在 UTC 时区中。在整个应用程序中始终使用 UTC 时区,将为使用 PHP 和 JavaScript 编写的其他日期处理库提供最大程度的互操作性。
如果将自定义格式应用于 date 或 datetime 转换(例如 datetime:Y-m-d H:i:s),则在日期序列化期间将使用 Carbon 实例的内部时区。通常,这将是应用程序 timezone 配置选项中指定的时区。但需要注意的是,timestamp 列(如 created_at 和 updated_at)不受此行为限制,无论应用程序的时区设置如何,它们始终以 UTC 格式化。
枚举转换
Eloquent 还允许将属性值转换为 PHP 枚举 (Enums)。为此,你可以在模型的 casts 方法中指定要转换的属性和枚举。
1use App\Enums\ServerStatus; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'status' => ServerStatus::class,12 ];13}
一旦在模型上定义了转换,当你与该属性交互时,指定的属性将自动在枚举之间进行转换。
1if ($server->status == ServerStatus::Provisioned) {2 $server->status = ServerStatus::Ready;3 4 $server->save();5}
转换枚举数组
有时你可能需要你的模型在单个列中存储枚举值数组。为此,你可以利用 Laravel 提供的 AsEnumArrayObject 或 AsEnumCollection 转换。
1use App\Enums\ServerStatus; 2use Illuminate\Database\Eloquent\Casts\AsEnumCollection; 3 4/** 5 * Get the attributes that should be cast. 6 * 7 * @return array<string, string> 8 */ 9protected function casts(): array10{11 return [12 'statuses' => AsEnumCollection::of(ServerStatus::class),13 ];14}
加密转换
encrypted 转换将使用 Laravel 内置的 加密 功能对模型属性值进行加密。此外,encrypted:array、encrypted:collection、encrypted:object、AsEncryptedArrayObject 和 AsEncryptedCollection 转换的工作方式与它们未加密的对应项类似;然而,正如你所预料的,底层值在存储到数据库时会被加密。
由于加密文本的最终长度不可预测且比其纯文本对应物长,请确保关联的数据库列类型为 TEXT 或更大。此外,由于值在数据库中是加密的,你将无法查询或搜索加密的属性值。
密钥轮换
如你所知,Laravel 使用应用程序 app 配置文件中指定的 key 配置值来加密字符串。通常,此值对应于 APP_KEY 环境变量的值。如果你需要轮换应用程序的加密密钥,可以 优雅地完成此操作。
查询时类型转换
有时你可能需要在执行查询时应用转换,例如从表中选择原始值时。例如,考虑以下查询:
1use App\Models\Post;2use App\Models\User;3 4$users = User::select([5 'users.*',6 'last_posted_at' => Post::selectRaw('MAX(created_at)')7 ->whereColumn('user_id', 'users.id')8])->get();
该查询结果中的 last_posted_at 属性将是一个简单的字符串。如果我们能在执行查询时对该属性应用 datetime 转换那就太好了。幸运的是,我们可以使用 withCasts 方法来实现这一点。
1$users = User::select([2 'users.*',3 'last_posted_at' => Post::selectRaw('MAX(created_at)')4 ->whereColumn('user_id', 'users.id')5])->withCasts([6 'last_posted_at' => 'datetime'7])->get();
自定义类型转换
Laravel 有多种内置的有用的转换类型;不过,你有时可能需要定义自己的转换类型。要创建转换,请执行 make:cast Artisan 命令。新的转换类将被放置在你的 app/Casts 目录中。
1php artisan make:cast AsJson
所有自定义转换类都实现 CastsAttributes 接口。实现此接口的类必须定义 get 和 set 方法。get 方法负责将数据库中的原始值转换为转换后的值,而 set 方法应将转换后的值转换为可存储在数据库中的原始值。作为示例,我们将把内置的 json 转换类型重新实现为自定义转换类型。
1<?php 2 3namespace App\Casts; 4 5use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 6use Illuminate\Database\Eloquent\Model; 7 8class AsJson implements CastsAttributes 9{10 /**11 * Cast the given value.12 *13 * @param array<string, mixed> $attributes14 * @return array<string, mixed>15 */16 public function get(17 Model $model,18 string $key,19 mixed $value,20 array $attributes,21 ): array {22 return json_decode($value, true);23 }24 25 /**26 * Prepare the given value for storage.27 *28 * @param array<string, mixed> $attributes29 */30 public function set(31 Model $model,32 string $key,33 mixed $value,34 array $attributes,35 ): string {36 return json_encode($value);37 }38}
定义好自定义转换类型后,你可以使用其类名将其附加到模型属性上。
1<?php 2 3namespace App\Models; 4 5use App\Casts\AsJson; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the attributes that should be cast.12 *13 * @return array<string, string>14 */15 protected function casts(): array16 {17 return [18 'options' => AsJson::class,19 ];20 }21}
值对象转换
你不局限于将值转换为原始类型。你也可以将值转换为对象。定义将值转换为对象的自定义转换与转换为原始类型非常相似;但是,如果你的值对象包含多个数据库列,则 set 方法必须返回一个键/值对数组,用于设置模型上的原始可存储值。如果你的值对象仅影响单个列,则只需返回可存储的值即可。
作为示例,我们将定义一个自定义转换类,将多个模型值转换为单个 Address 值对象。我们将假设 Address 值对象有两个公共属性:lineOne 和 lineTwo。
1<?php 2 3namespace App\Casts; 4 5use App\ValueObjects\Address; 6use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 7use Illuminate\Database\Eloquent\Model; 8use InvalidArgumentException; 9 10class AsAddress implements CastsAttributes11{12 /**13 * Cast the given value.14 *15 * @param array<string, mixed> $attributes16 */17 public function get(18 Model $model,19 string $key,20 mixed $value,21 array $attributes,22 ): Address {23 return new Address(24 $attributes['address_line_one'],25 $attributes['address_line_two']26 );27 }28 29 /**30 * Prepare the given value for storage.31 *32 * @param array<string, mixed> $attributes33 * @return array<string, string>34 */35 public function set(36 Model $model,37 string $key,38 mixed $value,39 array $attributes,40 ): array {41 if (! $value instanceof Address) {42 throw new InvalidArgumentException('The given value is not an Address instance.');43 }44 45 return [46 'address_line_one' => $value->lineOne,47 'address_line_two' => $value->lineTwo,48 ];49 }50}
当转换为值对象时,对值对象所做的任何更改都会在保存模型之前自动同步回模型。
1use App\Models\User;2 3$user = User::find(1);4 5$user->address->lineOne = 'Updated Address Value';6 7$user->save();
如果你计划将包含值对象的 Eloquent 模型序列化为 JSON 或数组,你应该在值对象上实现 Illuminate\Contracts\Support\Arrayable 和 JsonSerializable 接口。
值对象缓存
当被转换为值对象的属性被解析时,它们会被 Eloquent 缓存。因此,如果再次访问该属性,将返回相同的对象实例。
如果你想禁用自定义转换类的对象缓存行为,可以在自定义转换类上声明一个公共的 withoutObjectCaching 属性。
1class AsAddress implements CastsAttributes2{3 public bool $withoutObjectCaching = true;4 5 // ...6}
数组 / JSON 序列化
当使用 toArray 和 toJson 方法将 Eloquent 模型转换为数组或 JSON 时,你的自定义转换值对象通常也会被序列化,只要它们实现了 Illuminate\Contracts\Support\Arrayable 和 JsonSerializable 接口即可。但是,在使用第三方库提供的值对象时,你可能无法将这些接口添加到对象中。
因此,你可以指定你的自定义转换类负责序列化值对象。为此,你的自定义转换类应实现 Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 接口。此接口声明你的类应包含一个 serialize 方法,该方法应返回值对象的序列化形式。
1/** 2 * Get the serialized representation of the value. 3 * 4 * @param array<string, mixed> $attributes 5 */ 6public function serialize( 7 Model $model, 8 string $key, 9 mixed $value,10 array $attributes,11): string {12 return (string) $value;13}
入站转换
有时,你可能需要编写一个自定义转换类,它仅转换正在设置到模型上的值,而在从模型检索属性时不执行任何操作。
仅入站的自定义转换应实现 CastsInboundAttributes 接口,该接口仅要求定义一个 set 方法。可以使用 --inbound 选项调用 make:cast Artisan 命令来生成仅入站的转换类。
1php artisan make:cast AsHash --inbound
仅入站转换的一个经典示例是“哈希”转换。例如,我们可以定义一个通过给定算法对入站值进行哈希处理的转换。
1<?php 2 3namespace App\Casts; 4 5use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; 6use Illuminate\Database\Eloquent\Model; 7 8class AsHash implements CastsInboundAttributes 9{10 /**11 * Create a new cast class instance.12 */13 public function __construct(14 protected string|null $algorithm = null,15 ) {}16 17 /**18 * Prepare the given value for storage.19 *20 * @param array<string, mixed> $attributes21 */22 public function set(23 Model $model,24 string $key,25 mixed $value,26 array $attributes,27 ): string {28 return is_null($this->algorithm)29 ? bcrypt($value)30 : hash($this->algorithm, $value);31 }32}
转换参数
将自定义转换附加到模型时,可以通过使用 : 字符分隔类名并用逗号分隔多个参数来指定转换参数。这些参数将被传递给转换类的构造函数。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'secret' => AsHash::class.':sha256',10 ];11}
比较转换值
如果你想定义应如何比较两个给定的转换值以确定它们是否已更改,你的自定义转换类可以实现 Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes 接口。这使你可以细粒度地控制 Eloquent 认为哪些值已更改,从而在更新模型时保存到数据库。
此接口声明你的类应包含一个 compare 方法,如果给定的值被认为是相等的,则该方法应返回 true。
1/** 2 * Determine if the given values are equal. 3 * 4 * @param \Illuminate\Database\Eloquent\Model $model 5 * @param string $key 6 * @param mixed $firstValue 7 * @param mixed $secondValue 8 * @return bool 9 */10public function compare(11 Model $model,12 string $key,13 mixed $firstValue,14 mixed $secondValue15): bool {16 return $firstValue === $secondValue;17}
可转换对象 (Castables)
你可能希望允许应用程序的值对象定义它们自己的自定义转换类。除了将自定义转换类附加到你的模型外,你也可以选择附加一个实现了 Illuminate\Contracts\Database\Eloquent\Castable 接口的值对象类。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class,7 ];8}
实现 Castable 接口的对象必须定义一个 castUsing 方法,该方法返回负责与 Castable 类进行转换的自定义转换器类的类名。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Database\Eloquent\Castable; 6use App\Casts\AsAddress; 7 8class Address implements Castable 9{10 /**11 * Get the name of the caster class to use when casting from / to this cast target.12 *13 * @param array<string, mixed> $arguments14 */15 public static function castUsing(array $arguments): string16 {17 return AsAddress::class;18 }19}
使用 Castable 类时,你仍然可以在 casts 方法定义中提供参数。这些参数将被传递给 castUsing 方法。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class.':argument',7 ];8}
可转换对象 (Castables) 与匿名转换类
通过将“可转换对象”与 PHP 的 匿名类 结合使用,你可以将值对象及其转换逻辑定义为单个可转换对象。为此,从你的值对象的 castUsing 方法中返回一个匿名类。该匿名类应实现 CastsAttributes 接口。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Database\Eloquent\Castable; 6use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 7 8class Address implements Castable 9{10 // ...11 12 /**13 * Get the caster class to use when casting from / to this cast target.14 *15 * @param array<string, mixed> $arguments16 */17 public static function castUsing(array $arguments): CastsAttributes18 {19 return new class implements CastsAttributes20 {21 public function get(22 Model $model,23 string $key,24 mixed $value,25 array $attributes,26 ): Address {27 return new Address(28 $attributes['address_line_one'],29 $attributes['address_line_two']30 );31 }32 33 public function set(34 Model $model,35 string $key,36 mixed $value,37 array $attributes,38 ): array {39 return [40 'address_line_one' => $value->lineOne,41 'address_line_two' => $value->lineTwo,42 ];43 }44 };45 }46}