Eloquent: 变换器
介绍
访问器和变换器允许您在检索或设置模型实例上的 Eloquent 属性值时进行格式化。例如,您可能希望使用 Laravel 加密器 在将值存储到数据库时加密它,并在访问 Eloquent 模型上的属性时自动解密。
除了自定义访问器和变换器,Eloquent 还可以自动将日期字段转换为 Carbon 实例,甚至可以将文本字段 转换为 JSON。
访问器和变换器
定义访问器
要定义访问器,请在模型上创建一个 getFooAttribute
方法,其中 Foo
是您希望访问的列的 "studly" 大小写名称。在此示例中,我们将为 first_name
属性定义一个访问器。尝试检索 first_name
属性的值时,Eloquent 将自动调用该访问器:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取用户的名字。
*
* @param string $value
* @return string
*/
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
如您所见,列的原始值被传递给访问器,允许您操作并返回该值。要访问访问器的值,您可以访问模型实例上的 first_name
属性:
$user = App\User::find(1);
$firstName = $user->first_name;
您还可以使用访问器从现有属性返回新的计算值:
/**
* 获取用户的全名。
*
* @return string
*/
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
如果您希望将这些计算值添加到模型的数组 / JSON 表示中,您需要将它们附加。
定义变换器
要定义变换器,请在模型上定义一个 setFooAttribute
方法,其中 Foo
是您希望访问的列的 "studly" 大小写名称。同样,我们为 first_name
属性定义一个变换器。当我们尝试在模型上设置 first_name
属性的值时,将自动调用此变换器:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 设置用户的名字。
*
* @param string $value
* @return void
*/
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
变换器将接收正在设置在属性上的值,允许您操作该值并将操作后的值设置在 Eloquent 模型的内部 $attributes
属性上。例如,如果我们尝试将 first_name
属性设置为 Sally
:
$user = App\User::find(1);
$user->first_name = 'Sally';
在此示例中,将以值 Sally
调用 setFirstNameAttribute
函数。然后,变换器将对名称应用 strtolower
函数,并将其结果值设置在内部 $attributes
数组中。
日期变换器
默认情况下,Eloquent 会将 created_at
和 updated_at
列转换为 Carbon 实例,Carbon 扩展了 PHP 的 DateTime
类,并提供了一系列有用的方法。您可以通过设置模型的 $dates
属性来添加其他日期属性:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 应该被转换为日期的属性。
*
* @var array
*/
protected $dates = [
'seen_at',
];
}
您可以通过将模型的公共 $timestamps
属性设置为 false
来禁用默认的 created_at
和 updated_at
时间戳。
当某列被视为日期时,您可以将其值设置为 UNIX 时间戳、日期字符串 (Y-m-d
)、日期时间字符串或 DateTime
/ Carbon
实例。日期的值将被正确转换并存储在您的数据库中:
$user = App\User::find(1);
$user->deleted_at = now();
$user->save();
如上所述,当检索列在您的 $dates
属性中列出的属性时,它们将自动被转换为 Carbon 实例,允许您在属性上使用 Carbon 的任何方法:
$user = App\User::find(1);
return $user->deleted_at->getTimestamp();
日期格式
默认情况下,时间戳格式为 'Y-m-d H:i:s'
。如果您需要自定义时间戳格式,请在模型上设置 $dateFormat
属性。此属性决定日期属性在数据库中的存储方式:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* 模型日期列的存储格式。
*
* @var string
*/
protected $dateFormat = 'U';
}
属性类型转换
模型上的 $casts
属性提供了一种将属性转换为常见数据类型的便捷方法。$casts
属性应该是一个数组,其中键是要转换的属性的名称,值是您希望将列转换为的类型。支持的转换类型有:integer
、real
、float
、double
、decimal:<digits>
、string
、boolean
、object
、array
、collection
、date
、datetime
和 timestamp
。转换为 decimal
时,您必须定义位数 (decimal:2
)。
为了演示属性类型转换,让我们将 is_admin
属性从数据库中存储为整数 (0
或 1
) 转换为布尔值:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 应该被转换的属性。
*
* @var array
*/
protected $casts = [
'is_admin' => 'boolean',
];
}
现在,当您访问 is_admin
属性时,它将始终被转换为布尔值,即使底层值在数据库中存储为整数:
$user = App\User::find(1);
if ($user->is_admin) {
//
}
为 null
的属性将不会被转换。此外,您不应定义与关系同名的转换(或属性)。
自定义类型转换
Laravel 提供了多种内置的、实用的转换类型;然而,您可能偶尔需要定义自己的转换类型。您可以通过定义一个实现 CastsAttributes
接口的类来实现这一点。
实现此接口的类必须定义一个 get
和 set
方法。get
方法负责将数据库中的原始值转换为转换后的值,而 set
方法应将转换后的值转换为可以存储在数据库中的原始值。作为示例,我们将重新实现内置的 json
转换类型作为自定义转换类型:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Json implements CastsAttributes
{
/**
* 转换给定的值。
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
*/
public function get($model, $key, $value, $attributes)
{
return json_decode($value, true);
}
/**
* 准备给定的值以进行存储。
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return json_encode($value);
}
}
一旦您定义了自定义转换类型,您可以使用其类名将其附加到模型属性:
<?php
namespace App;
use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 应该被转换的属性。
*
* @var array
*/
protected $casts = [
'options' => Json::class,
];
}
值对象转换
您不仅限于将值转换为基本类型。您还可以将值转换为对象。定义将值转换为对象的自定义转换与转换为基本类型非常相似;然而,set
方法应返回一个键/值对数组,这些数组将用于在模型上设置原始、可存储的值。
作为示例,我们将定义一个自定义转换类,该类将多个模型值转换为单个 Address
值对象。我们假设 Address
值具有两个公共属性:lineOne
和 lineTwo
:
<?php
namespace App\Casts;
use App\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use InvalidArgumentException;
class Address implements CastsAttributes
{
/**
* 转换给定的值。
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \App\Address
*/
public function get($model, $key, $value, $attributes)
{
return new Address(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
/**
* 准备给定的值以进行存储。
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param \App\Address $value
* @param array $attributes
* @return array
*/
public function set($model, $key, $value, $attributes)
{
if (! $value instanceof Address) {
throw new InvalidArgumentException('给定的值不是 Address 实例。');
}
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
}
当转换为值对象时,对值对象所做的任何更改将在模型保存之前自动同步回模型:
$user = App\User::find(1);
$user->address->lineOne = 'Updated Address Value';
$user->save();
如果您计划将包含值对象的 Eloquent 模型序列化为 JSON 或数组,您应该在值对象上实现 Illuminate\Contracts\Support\Arrayable
和 JsonSerializable
接口。
入站转换
有时,您可能需要编写一个仅转换设置在模型上的值的自定义转换,而在从模型检索属性时不执行任何操作。入站仅自定义转换应实现 CastsInboundAttributes
接口,该接口仅要求定义一个 set
方法。
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
class Hash implements CastsInboundAttributes
{
/**
* 哈希算法。
*
* @var string
*/
protected $algorithm;
/**
* 创建一个新的转换类实例。
*
* @param string|null $algorithm
* @return void
*/
public function __construct($algorithm = null)
{
$this->algorithm = $algorithm;
}
/**
* 准备给定的值以进行存储。
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return is_null($this->algorithm)
? bcrypt($value)
: hash($this->algorithm, $value);
}
}
转换参数
在将自定义转换附加到模型时,可以通过使用 :
字符分隔类名并用逗号分隔多个参数来指定转换参数。参数将传递给转换类的构造函数:
/**
* 应该被转换的属性。
*
* @var array
*/
protected $casts = [
'secret' => Hash::class.':sha256',
];
可转换
您可以选择不将自定义转换附加到模型,而是附加一个实现 Illuminate\Contracts\Database\Eloquent\Castable
接口的类:
protected $casts = [
'address' => \App\Address::class,
];
实现 Castable
接口的对象必须定义一个 castUsing
方法,该方法返回负责从 Castable
类转换到和从 Castable
类转换的自定义转换器类的类名:
<?php
namespace App;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;
class Address implements Castable
{
/**
* 获取在从 / 到此转换目标进行转换时使用的转换器类的名称。
*
* @return string
*/
public static function castUsing()
{
return AddressCast::class;
}
}
使用 Castable
类时,您仍然可以在 $casts
定义中提供参数。参数将直接传递给转换器类:
protected $casts = [
'address' => \App\Address::class.':argument',
];
数组和 JSON 类型转换
array
转换类型在处理存储为序列化 JSON 的列时特别有用。例如,如果您的数据库有一个 JSON
或 TEXT
字段类型,其中包含序列化的 JSON,向该属性添加 array
转换将自动在您访问 Eloquent 模型上的属性时将其反序列化为 PHP 数组:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 应该被转换的属性。
*
* @var array
*/
protected $casts = [
'options' => 'array',
];
}
一旦定义了转换,您可以访问 options
属性,它将自动从 JSON 反序列化为 PHP 数组。当您设置 options
属性的值时,给定的数组将自动序列化回 JSON 以进行存储:
$user = App\User::find(1);
$options = $user->options;
$options['key'] = 'value';
$user->options = $options;
$user->save();
日期类型转换
使用 date
或 datetime
转换类型时,您可以指定日期的格式。此格式将在 模型序列化为数组或 JSON 时使用:
/**
* 应该被转换的属性。
*
* @var array
*/
protected $casts = [
'created_at' => 'datetime:Y-m-d',
];
查询时类型转换
有时您可能需要在执行查询时应用转换,例如在从表中选择原始值时。例如,考虑以下查询:
use App\Post;
use App\User;
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->get();
此查询结果中的 last_posted_at
属性将是一个原始字符串。如果我们可以在执行查询时对该属性应用 date
转换,那将很方便。为此,我们可以使用 withCasts
方法:
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
'last_posted_at' => 'datetime'
])->get();