Laravel 7.x Eloquent:ミューテタ

イントロダクション

アクセサとミューテタはモデルの取得や値を設定するときに、Eloquent属性のフォーマットを可能にします。たとえばLaravelの暗号化を使いデータベース保存時に値を暗号化し、Eloquentモデルでアクセスする時には自動的にその属性を復元するように設定できます。

カスタムのアクセサやミューテタに加え、Eloquentは日付フールドを自動的にCarbonインスタンスにキャストしますし、テキストフィールドをJSONにキャストすることもできます。

アクセサとミューテタ

アクセサの定義

アクセサを定義するには、アクセスしたいカラム名が「studlyケース(Upper Camel Case)」でFooの場合、getFooAttributeメソッドをモデルに作成します。以下の例では、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}";
}

Tip!! これらの計算済みの値をモデルのarray/JSON表現に追加したい場合は、プロパティに追加する必要があります

ミューテタの定義

ミューテタを定義するにはアクセスしたいカラム名がFooの場合、モデルに「ローワーキャメルケース」でsetFooAttributeメソッドを作成します。今回も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内部プロパティへ加工済みの値を設定します。ではSallyfirst_name属性へ設定してみましょう。

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

$user->first_name = 'Sally';

上記の場合、setFirstNameAttributeメソッドが呼び出され、Sallyの値が渡されます。このミューテタはそれから名前にstrtolowerを適用し、その値を$attributes内部配列へ設定します。

日付ミューテタ

デフォルトでEloquentはcreated_atupdated_atカラムをCarbonインスタンスへ変換します。CarbonはPHPネイティブのDateTimeクラスを拡張しており、便利なメソッドを色々と提供しています。モデルの$datesプロパティをセットすることにより、データ属性を追加できます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 日付を変形する属性
     *
     * @var array
     */
    protected $dates = [
        'seen_at',
    ];
}

Tip!! モデルの$timestampsプロパティをfalseへセットすることにより、デフォルトのcreated_atupdated_atタイムスタンプを無効にできます。

日付だと推定されるカラムで、値はUnixタイムスタンプ、日付文字列(Y-m-d)、日付時間文字列、DateTimeCarbonインスタンスを値としてセットできます。日付の値は自動的に正しく変換され、データベースへ保存されます。

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

$user->deleted_at = now();

$user->save();

前記の通り$datesプロパティにリストした属性を取得する場合、自動的にCarbonインスタンスへキャストされますので、その属性でCarbonのメソッドがどれでも使用できます。

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

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

Dateフォーマット

デフォルトのタイムスタンプフォーマットは'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プロパティは配列で、キーにはキャストする属性名を指定し、値にはそのカラムに対してキャストしたいタイプを指定します。サポートしているキャストタイプはintegerrealfloatdoubledecimal:<桁数>stringbooleanobjectarraycollectiondatedatetimetimestampです。decimalへキャストする場合は、桁数をdecimal:2のように定義してください。

属性キャストのデモンストレーションとして、データベースには整数の01で保存されているis_admin属性を論理値にキャストしてみましょう。

<?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) {
    //
}

Note: 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;

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

class User extends Model
{
    /**
     * キャストする属性
     *
     * @var array
     */
    protected $casts = [
        'options' => Json::class,
    ];
}

値オブジェクトのキャスト

値のキャストはプリミティブタイプに限定されていません。キャスト値をオブジェクトにすることもできます。値をオブジェクトに変換する定義は、プリミティブタイプへの変換にとても似ています。しかしながら、setメソッドはキー/値ペアの配列を返す必要があります。モデルへ保存可能な値として、元の値をセットするために使用されます。

例として複数のモデルの値をひとつのAddressにキャストする、カスタムキャストクラスを定義してみましょう。Address値はlineOnelineTwo、2つのパブリックプロパティを持つと仮定しましょう。

<?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('The given value is not an Address instance.');
        }

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

値オブジェクトへのキャスト時は、モデルが保存される前に値オブジェクトへ行われた変更が、自動でモデルへ同期されます。

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

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

$user->save();

Tip!! 値オブジェクトを含む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',
];

Castable

モデルにカスタムキャストを指定する代わりに、Illuminate\Contracts\Database\Eloquent\Castableインターフェイスを実装するクラスを指定することも可能です。

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

Castableインターフェイスを実装するオブジェクトは、castUsingメソッドを定義する必要があります。このメソッドは、キャストに責任を持つカスタムキャスタクラスのクラス名を返します。

<?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を持つJSONTEXTフィールドがある場合です。その属性にarrayキャストを追加すれば、Eloquentモデルにアクセスされた時点で自動的に非シリアライズ化され、PHPの配列へとキャストされます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * キャストする属性
     *
     * @var array
     */
    protected $casts = [
        'options' => 'array',
    ];
}

キャストを定義後、options属性にアクセスすると自動的に非シリアライズされPHP配列になります。options属性へ値をセットすると配列は保存のために自動的にJSONへシリアライズされます。

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

$options = $user->options;

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

$user->options = $options;

$user->save();

日付のキャスト

datedatetimeキャストタイプを使用する場合、日付のフォーマットを指定できます。このフォーマットは、モデルを配列や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();

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる