Laravel 8.x Eloquent:ミューテタ/キャスト

イントロダクション

アクセサ、ミューテタ、および属性キャストを使用すると、Eloquentモデルインスタンスで属性値を取得または設定するときに、それらの属性値を変換できます。たとえば、Laravel暗号化を使用して、データベースに保存されている値を暗号化し、Eloquentモデル上でそれにアクセスしたときに属性を自動的に復号できます。他に、Eloquentモデルを介してアクセスするときに、データベースに格納されているJSON文字列を配列に変換することもできます。

アクセサ/ミューテタ

アクセサの定義

アクセサは、アクセス時にEloquent属性値を変換します。アクセサを定義するには、モデルにget{Attribute}Attributeメソッドを作成します。{Attribute}は、アクセスするカラムのアッパーキャメルケース(studly case)の名前です。

この例では、first_name属性のアクセサを定義します。アクセサは、first_name属性の値を取得しようとすると、Eloquentによって自動的に呼び出されます。

<?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属性にアクセスするだけです。

use App\Models\User;

$user = User::find(1);

$firstName = $user->first_name;

アクセサは単一の属性の操作に限定されません。アクセサを使用して、既存の属性から新しい計算値を返すこともできます。

/**
 * ユーザーのフルネームの取得
 *
 * @return string
 */
public function getFullNameAttribute()
{
    return "{$this->first_name} {$this->last_name}";
}

Tip!! こうした計算値をモデルの配列/JSON表現に追加したい場合は、手動で追加する必要があります

ミューテタの定義

ミューテタは、設定時にEloquent属性値を変換します。ミューテタを定義するには、モデルでset{Attribute}Attributeメソッドを定義します。{Attribute}は、アクセスするカラム名のアッパーキャメルケース(studly case)です。

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プロパティに設定します。ミューテタを使用するには、Eloquentモデルに対し、first_name属性を設定するだけです。

use App\Models\User;

$user = User::find(1);

$user->first_name = 'Sally';

この例で、setFirstNameAttribute関数はSally値で呼び出されます。次に、ミューテタは名前にstrtolower関数を適用し、その結果の値を内部の$attributes配列へ設定します。

属性のキャスト

属性キャストは、モデルで追加のメソッドを定義することなく、アクセサやミューテタと同様の機能を提供します。定義する代わりに、モデルの$castsプロパティにより属性を一般的なデータ型に変換する便利な方法を提供します。

$castsプロパティは、キーがキャストする属性の名前であり、値がそのカラムをキャストするタイプである配列である必要があります。サポートしているキャストタイプは以下のとおりです。

  • array
  • AsStringable::class
  • boolean
  • collection
  • date
  • datetime
  • immutable_date
  • immutable_datetime
  • decimal:<digits>
  • double
  • encrypted
  • encrypted:array
  • encrypted:collection
  • encrypted:object
  • float
  • integer
  • object
  • real
  • string
  • timestamp

属性のキャストをデモンストレートするため、データベースに整数(0または1)として格納している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) {
    //
}

実行時に新しく一時的なキャストを追加する必要がある場合は、mergeCastsメソッドを使用します。こうしたキャストの定義は、モデルで既に定義しているキャストのいずれかに追加されます。

$user->mergeCasts([
    'is_admin' => 'integer',
    'options' => 'object',
]);

Note: nullである属性はキャストしません。また、リレーションと同じ名前のキャスト(または属性)を定義しないでください。

Stringableのキャスト

モデルの属性をfluentのIlluminate\Support\Stringableオブジェクトへキャストするには、Illuminate\Database\Eloquent\Casts\AsStringableキャストクラスが使用できます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;

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

配列とJSONのキャスト

arrayキャストは、シリアル化されたJSONとして保存されているカラムを操作するときに特に役立ちます。たとえば、データベースにシリアル化されたJSONを含むJSONまたはTEXTフィールドタイプがある場合、その属性へarrayキャストを追加すると、Eloquentモデル上でアクセス時に、属性が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に戻されて保存されます。

use App\Models\User;

$user = User::find(1);

$options = $user->options;

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

$user->options = $options;

$user->save();

JSON属性の単一のフィールドをより簡潔な構文で更新するには、updateメソッドを呼び出すときに->演算子を使用します。

$user = User::find(1);

$user->update(['options->key' => 'value']);

配列オブジェクトとコレクションのキャスト

多くのアプリケーションには標準のarrayキャストで十分ですが、いくつかの欠点を持ちます。arrayキャストはプリミティブ型を返すので、配列のオフセットを直接変更することはできません。たとえば、次のコードはPHPエラーを起こします。

$user = User::find(1);

$user->options['key'] = $value;

これを解決するために、Laravelは、JSON属性をArrayObjectクラスにキャストするasArrayObjectキャストを提供します。この機能はLaravelのカスタムキャストの実装を使用しており、Laravelがインテリジェントにキャッシュし、PHPエラーを引き起こすことなく、個々のオフセットを変更できるように、ミューテートしたオブジェクトを変換することができます。AsArrayObject`のキャストを使用するには、単純に属性に割り当てるだけです。

use Illuminate\Database\Eloquent\Casts\AsArrayObject;

/**
 * キャストする属性
 *
 * @var array
 */
protected $casts = [
    'options' => AsArrayObject::class,
];

同様に、LaravelはJSON属性をLaravelコレクションへキャストするASCollectionキャストを提供しています

use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * キャストする属性
 *
 * @var array
 */
protected $casts = [
    'options' => AsCollection::class,
];

日付のキャスト

デフォルトでは、Eloquentはcreated_atカラムとupdated_atカラムをCarbonのインスタンスへキャストします。これによりPHPのDateTimeクラスを拡張した、多くの便利なメソッドが提供されます。モデルの$castsプロパティ配列内で日付キャストを追加定義すれば、他の日付属性をキャストできます。通常、日付はdatetimeimmutable_datetimeキャストタイプを使用してキャストする必要があります。

dateまたはdatetimeキャストを定義するときに、日付の形式を指定することもできます。この形式は、モデルが配列またはJSONにシリアル化される場合に使用されます。

/**
 * キャストする必要のある属性
 *
 * @var array
 */
protected $casts = [
    'created_at' => 'datetime:Y-m-d',
];

カラムが日付へキャストされる場合、対応するモデル属性の値として、UNIXのタイムスタンプ、日付文字列(Y-m-d)、日付時間文字列、またはDateTimeCarbonインスタンスを設定することができます。日付の値は正しく変換され、データベースへ保存されます。

モデルにserializeDateメソッドを定義することで、モデルのすべての日付のデフォルトのシリアル化形式をカスタマイズできます。この方法は、データベースへ保存するために日付をフォーマットする方法には影響しません。

/**
 * 配列/JSONシリアル化の日付を準備
 *
 * @param  \DateTimeInterface  $date
 * @return string
 */
protected function serializeDate(DateTimeInterface $date)
{
    return $date->format('Y-m-d');
}

データベース内にモデルの日付を実際に保存するときに使用する形式を指定するには、モデルに$dateFormatプロパティを定義する必要があります。

/**
 * モデルの日付カラムのストレージ形式
 *
 * @var string
 */
protected $dateFormat = 'U';

日付のキャストとシリアライズ、タイムゾーン

datedatetimeのキャストはデフォルトで、アプリケーションのtimezone設定オプションで指定されているタイムゾーンに関わらず、日付をUTC ISO-8601の日付文字列(1986-05-28T21:05:54.000000Z)にシリアライズします。アプリケーションのtimezone設定オプションをデフォルトのUTCから変更せずに、常にこのシリアライズ形式を使用し、アプリケーションの日付をUTCタイムゾーンで保存することを強く推奨します。アプリケーション全体でUTCタイムゾーンを一貫して使用することで、PHPやJavaScriptで書かれた他の日付操作ライブラリとの相互運用性を最大限に高められます。

datetime:Y-m-d H:i:sのようなカスタムフォーマットをdatedatetimeキャストで適用する場合は、日付のシリアライズの際に、Carbonインスタンスの内部タイムゾーンが使用されます。一般的には、アプリケーションのtimezone設定オプションで指定したタイムゾーンを使用します。

Enumキャスト

Note: EnumキャストはPHPバージョン8.1以上でのみ利用可能です。

Eloquentでは、属性値をPHPの列挙型にキャストすることもできます。それには、キャストしたい属性とenumをモデルの$castsプロパティ配列で指定します。

use App\Enums\ServerStatus;

/**
 * キャストする属性
 *
 * @var array
 */
protected $casts = [
    'status' => ServerStatus::class,
];

モデルにキャストを定義すると、指定した属性を操作する際に、自動的にenumへキャストしたり、enumからキャストされたりするようになります。

if ($server->status == ServerStatus::provisioned) {
    $server->status = ServerStatus::ready;

    $server->save();
}

暗号化キャスト

encryptedキャストは、Laravelに組み込まれた暗号化機能を使って、モデルの属性値を暗号化します。さらに、encrypted:arrayencrypted:collectionencrypted:objectAsEncryptedArrayObjectAsEncryptedCollectionのキャストは、暗号化されていないものと同様の動作をしますが、ご期待通りにデータベースに保存される際に、基本的な値を暗号化します。

暗号化したテキストの最終的な長さは予測できず、プレーンテキストのものよりも長くなるので、関連するデータベースのカラムが TEXT 型以上であることを確認してください。さらに、値はデータベース内で暗号化されているので、暗号化された属性値を問い合わせたり検索したりすることはできません。

クエリ時のキャスト

テーブルから元の値でセレクトするときなど、クエリの実行中にキャストを適用する必要が起きる場合があります。たとえば、次のクエリを考えてみましょう。

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属性は単純な文字列になります。クエリを実行するときに、この属性に「datetime」キャストを適用できれば素晴らしいと思うでしょう。幸運なことに、withCastsメソッドを使用してこれができます。

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
            ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'datetime'
])->get();

カスタムキャスト

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\Models;

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\Models\Address as AddressModel;
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 AddressModel(
            $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 AddressModel) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

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

値オブジェクトにキャストする場合、値オブジェクトに加えられた変更は、モデルが保存される前に自動的にモデルに同期されます。

use App\Models\User;

$user = User::find(1);

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

$user->save();

Tip!! 値オブジェクトを含むEloquentモデルをJSONまたは配列にシリアル化する場合は、値オブジェクトにIlluminate\Contracts\Support\ArrayableおよびJsonSerializableインターフェイスを実装する必要があります。

配列/JSONのシリアル化

EloquentモデルをtoArrayおよびtoJsonメソッドを使用して配列やJSONへ変換する場合、カスタムキャスト値オブジェクトは通常、Illuminate\Contracts\Support\ArrayableおよびJsonSerializableインターフェイスを実装している限りシリアル化されます。しかし、サードパーティライブラリによって提供される値オブジェクトを使用する場合、これらのインターフェイスをオブジェクトに追加できない場合があります。

したがって、カスタムキャストクラスが値オブジェクトのシリアル化を担当するように指定できます。そのためには、カスタムクラスキャストでIlluminate\Contracts\Database\Eloquent\SerializesCastableAttributesインターフェイスを実装する必要があります。このインターフェイスは、クラスに「serialize」メソッドが含まれている必要があることを示しています。このメソッドは、値オブジェクトのシリアル化された形式を返す必要があります。

/**
 * 値をシリアル化した表現の取得
 *
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  string  $key
 * @param  mixed  $value
 * @param  array  $attributes
 * @return mixed
 */
public function serialize($model, string $key, $value, array $attributes)
{
    return (string) $value;
}

インバウンドのキャスト

場合によっては、モデルに値を設定するときのみ変換し、モデルから属性を取得するときは操作をしないカスタムキャストを作成する必要があります。インバウンドのみのキャストの典型的な例は、「ハッシュ(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インターフェイスを実装する値オブジェクトクラスをアタッチすることもできます。

use App\Models\Address;

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

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

<?php

namespace App\Models;

use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;

class Address implements Castable
{
    /**
     * このキャストターゲットにキャストする/されるときに使用するキャスタークラスの名前を取得
     *
     * @param  array  $arguments
     * @return string
     */
    public static function castUsing(array $arguments)
    {
        return AddressCast::class;
    }
}

Castableクラスを使用する場合でも、$casts定義に引数を指定できます。引数はcastUsingメソッドに渡されます。

use App\Models\Address;

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

Castableと匿名キャストクラス

"Castable"をPHPの匿名クラスと組み合わせることで、値オブジェクトとそのキャストロジックを単一のCastableオブジェクトとして定義できます。これを実現するには、値オブジェクトのcastUsingメソッドから匿名クラスを返します。匿名クラスはCastsAttributesインターフェイスを実装する必要があります。

<?php

namespace App\Models;

use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Address implements Castable
{
    // ...

    /**
     * このキャストターゲットにキャストする/されるときに使用するキャスタークラスの名前を取得
     *
     * @param  array  $arguments
     * @return object|string
     */
    public static function castUsing(array $arguments)
    {
        return new class implements CastsAttributes
        {
            public function get($model, $key, $value, $attributes)
            {
                return new Address(
                    $attributes['address_line_one'],
                    $attributes['address_line_two']
                );
            }

            public function set($model, $key, $value, $attributes)
            {
                return [
                    'address_line_one' => $value->lineOne,
                    'address_line_two' => $value->lineTwo,
                ];
            }
        };
    }
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュを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)へ移動

その他

?

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