修改器

未匹配的标注

Eloquent: 修改器

简介

当你在 Eloquent 模型实例中获取或设置某些属性值的时候,访问器和修改器允许你对 Eloquent 属性值进行格式化。例如,你可能需要使用 Laravel 加密器 来加密保存在数据库中的值,而在使用 Eloquent 模型访问该属性的时候自动进行解密其值。

除了自定义访问器和修改器外,Eloquent 也会自动将日期字段类型转换为 Carbon 实例,或将 文本类型转换为 JSON

访问器 & 修改器

定义一个访问器

若要定义一个访问器, 则需在模型上创建一个 getFooAttribute 方法,要访问的 Foo 字段需使用「驼峰式」命名。 在这个示例中,我们将为 first_name 属性定义一个访问器。当 Eloquent 尝试获取 first_name 属性时,将自动调用此访问器:

<?php

namespace App\Models;

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\Models\User::find(1);

$firstName = $user->first_name;

当然,你也可以通过已有的属性值,使用访问器返回新的计算值:

/**
 * 获取用户全名
 *
 * @return string
 */
public function getFullNameAttribute()
{
    return "{$this->first_name} {$this->last_name}";
}

技巧:如果你需要将这些计算值添加到模型的 Array / JSON, 你需要追加它们

定义一个修改器

若要定义一个修改器,则需在模型上面定义 setFooAttribute 方法。要访问的 Foo 字段使用「驼峰式」命名。让我们再来定义一个 first_name 属性的修改器。当我们尝试使用 first_name 属性值时,该修改器将被自动调用:

<?php

namespace App\Models;

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\Models\User::find(1);

$user->first_name = 'Sally';

在本例中, setFirstNameAttribute 方法在调用的时候赋予 Sally这个值作为参数。然后修改器会使用 strtolower 函数处理并将结果设置到内部的 $attributes 数组。

日期转换器

默认情况下,Eloquent 会将 created_atupdated_at 字段转换为 Carbon实例, 它继承了 PHP 原生的 DateTime 类并提供了各种有用的方法。 你可以通过设置模型的 $dates属性来添加其他日期属性:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * $dates 数组内的列转换为日期格式
     *
     * @var array
     */
    protected $dates = [
        'seen_at',
    ];
}

技巧:你可以通过将模型的公有属性 $timestamps 设置为 false 来禁用默认的 created_atupdated_at 的时间戳。

当某个字段是日期格式时,你可以将值设置为一个 UNIX 时间戳,日期时间 (Y-m-d)字符串或者 DateTime / Carbon 实例。日期值会被正确格式化并保存到你的数据库中:

$user = App\Models\User::find(1);

$user->deleted_at = now();

$user->save();

就像上面所说的,当获取到的属性包含在 $dates 属性中时,都会自动转换为 Carbon 实例,允许你在属性上使用任意的 Carbon 方法:

$user = App\Models\User::find(1);

return $user->deleted_at->getTimestamp();

日期格式

默认情况下时间戳都将以 'Y-m-d H:i:s'形式格式化。如果你需要自定义时间戳格式,可在模型中设置 $dateFormat 属性。这个属性决定了日期属性将以何种形式保存在数据库中:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 这个时间字段被为被格式化为UNIX 时间戳的形式存储
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

属性类型转换

模型中的 $casts 属性提供了一个便利的方法来将属性转换为常见的数据类型。$casts 属性应是一个数组,且数组的键是那些需要被转换的属性名称,值则是你希望转换的数据类型。支持转换的数据类型有:integerrealfloatdoubledecimal:<digits>stringbooleanobjectarraycollectiondatedatetimetimestamp。当需要转换为 decimal 类型时,你需要定义小数位的个数,如:(decimal:2)。

示例,让我们把以整数 (01) 形式存储在数据库中的 is_admin 属性转成布尔值:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 强制转换的属性
     *
     * @var array
     */
    protected $casts = [
        'is_admin' => 'boolean',
    ];
}

现在当你访问 is_admin 属性时,虽然保存在数据库里的值是一个整数类型,但是返回值总是会被转换成布尔值类型:

$user = App\Models\User::find(1);

if ($user->is_admin) {
    //
}

注意:为 null 的属性将不会被强制转换。另外,永远不要定义与关系(或属性)同名的强制转换。

自定义类型转换

Laravel 内置了多种常用的类型转换。但是,用户偶尔会需要将数据转换成自定义类型。现在,该需求可以通过定义一个实现 CastsAttributes 接口的类来完成。

实现了该接口的类必须事先定义一个 getset 方法。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\Models;

use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'options' => Json::class,
    ];
}

值对象类型转换

你不仅可以将数据转换成原生的数据类型,还可以将数据转换成对象。两种自定义类型转换的定义方式非常类似。但是将数据转换成对象的自定义转换类中的 set 方法需要返回键值对数组,用于设置原始、可存储的值到对应的模型中。

举个例子,定义一个自定义类型转换类用于将多个模型属性值转换成单个 Address 值对象,假设 Address 对象有两个公有属性 lineOnelineTwo

<?php

namespace App\Casts;

use App\Models\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\Models\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\Models\Address  $value
     * @param  array  $attributes
     * @return array
     */
    public function set($model, $key, $value, $attributes)
    {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}

进行值对象类型转换后,任何对值对象的数据转换将会自动同步回模型中:

$user = App\Models\User::find(1);

$user->address->lineOne = 'Updated Address Value';

$user->save();

技巧:如果想将包含值对象的 Eloquent 模型序列化为 JSON 或数组,你只需让该模型实现 Illuminate\Contracts\Support\ArrayableJsonSerializable 接口即可。

映射类型转换

有时候,你可能只需要对写入模型的属性值进行类型转换而不需要对从模型中获取的属性值进行任何处理。一个典型映射类型转换的例子就是 「hashing」。类型转换类需要实现 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',
];

Castables

除了将自定义类型转换附加到模型之外,您还可以附加一个实现 Illuminate\Contracts\Database\Eloquent\Castable 接口的类:

protected $casts = [
    'address' => \App\Models\Address::class,
];

实现 Castable 接口的对象必须定义一个 castUsing 方法,该方法返回负责在 Castable 类之间进行转换的自定义 Caster 类的类名:

<?php

namespace App\Models;

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 中定义参数。 参数将直接传递到 Caster 类:

protected $casts = [
    'address' => \App\Models\Address::class.':argument',
];

数组 & JSON 转换

当你在数据库存储序列化的 JSON 的数据时,array 类型的转换非常有用。比如:如果你的数据库具有被序列化为 JSON 的 JSONTEXT 字段类型,并且在 Eloquent 模型中加入了 array 类型转换,那么当你访问的时候就会自动被转换为 PHP 数组:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 应进行类型转换的属性
     *
     * @var array
     */
    protected $casts = [
        'options' => 'array',
    ];
}

一旦定义了转换,你访问 options 属性时他会自动从 JSON 类型反序列化为 PHP 数组。当你设置了 options 属性的值时,给定的数组也会自动序列化为 JSON 类型存储:

$user = App\Models\User::find(1);

$options = $user->options;

$options['key'] = 'value';

$user->options = $options;

$user->save();

Date 类型转换

当使用 datedatetime 属性时,可以指定日期的格式。 这种格式会被用在 模型序列化为数组或者 JSON

/**
 * The attributes that should be cast.
 *
 * @var array
 */
protected $casts = [
    'created_at' => 'datetime:Y-m-d',
];

查询时类型转换

有时候需要在查询执行过程中对特定属性进行类型转换,例如需要从数据库表中获取数据的时候。举个例子,请参考以下查询:

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')
])->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();

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:7
讨论数量: 0
发起讨论 只看当前版本


暂无话题~