はじめに
アクセサ、ミューテータ、および属性キャスティングを使用すると、モデルインスタンスで属性値を取得または設定する際にEloquent属性値を変換できます。たとえば、データベースに保存されている間に値を暗号化するためにLaravelエンクリプターを使用し、Eloquentモデルでアクセスする際に属性を自動的に復号化することができます。または、データベースに保存されているJSON文字列をEloquentモデルを介してアクセスする際に配列に変換したい場合もあります。
アクセサとミューテータ
アクセサの定義
アクセサは、Eloquent属性値がアクセスされるときに変換されます。アクセサを定義するには、モデルに保護されたメソッドを作成してアクセス可能な属性を表します。このメソッド名は、適用可能な場合、真の基礎となるモデル属性/データベース列の「キャメルケース」表現に対応する必要があります。
この例では、first_name
属性のアクセサを定義します。アクセサは、first_name
属性の値を取得しようとする際にEloquentによって自動的に呼び出されます。すべての属性アクセサ/ミューテータメソッドは、Illuminate\Database\Eloquent\Casts\Attribute
の戻り値の型ヒントを宣言する必要があります:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
}
すべてのアクセサメソッドは、属性がどのようにアクセスされ、オプションで変更されるかを定義するAttribute
インスタンスを返します。この例では、属性がどのようにアクセスされるかを定義するだけです。そのために、get
引数をAttribute
クラスコンストラクタに提供します。
ご覧のとおり、列の元の値がアクセサに渡され、値を操作して返すことができます。アクセサの値にアクセスするには、モデルインスタンスのfirst_name
属性に単純にアクセスできます:
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;
計算されたこれらの値をモデルの配列/JSON表現に追加したい場合は、それらを追加する必要があります。
複数属性からの値オブジェクトの構築
時には、アクセサが複数のモデル属性を単一の「値オブジェクト」に変換する必要があります。そのために、get
クロージャは、$attributes
の2番目の引数を受け入れることができ、これは自動的にクロージャに供給され、モデルの現在のすべての属性の配列を含みます:
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
);
}
アクセサキャッシング
アクセサから値オブジェクトを返すとき、値オブジェクトに加えられた変更は、モデルが保存される前に自動的にモデルに同期されます。これは、Eloquentがアクセサによって返されたインスタンスを保持するため、アクセサが呼び出されるたびに同じインスタンスを返すことができるからです:
use App\Models\User;
$user = User::find(1);
$user->address->lineOne = 'Updated Address Line 1 Value';
$user->address->lineTwo = 'Updated Address Line 2 Value';
$user->save();
ただし、文字列やブール値などのプリミティブ値のキャッシングを有効にしたい場合があります。特に計算集約が多い場合はそうです。これを実現するには、アクセサを定義するときにshouldCache
メソッドを呼び出すことができます:
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}
属性のオブジェクトキャッシング動作を無効にしたい場合は、属性を定義するときにwithoutObjectCaching
メソッドを呼び出すことができます:
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
)->withoutObjectCaching();
}
ミューテータの定義
ミューテータは、Eloquent属性値が設定されるときに変換されます。ミューテータを定義するには、属性を定義するときにset
引数を提供できます。first_name
属性のミューテータを定義しましょう。このミューテータは、モデルのfirst_name
属性の値を設定しようとする際に自動的に呼び出されます:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Interact with the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
}
ミューテータクロージャは、属性に設定されている値を受け取り、値を操作して操作された値を返すことができます。ミューテータを使用するには、Eloquentモデルのfirst_name
属性を設定するだけです:
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';
この例では、set
コールバックが値Sally
で呼び出されます。ミューテータは、strtolower
関数を名前に適用し、その結果の値をモデルの内部$attributes
配列に設定します。
複数属性のミューテーション
時には、ミューテータが基礎となるモデルの複数の属性を設定する必要があります。そのために、set
クロージャから配列を返すことができます。配列の各キーは、モデルに関連付けられた基礎となる属性/データベース列に対応する必要があります:
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
set: fn (Address $value) => [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
],
);
}
属性キャスティング
属性キャスティングは、モデルに追加のメソッドを定義することなく、アクセサやミューテータに似た機能を提供します。代わりに、モデルのcasts
メソッドは、属性を一般的なデータ型に変換する便利な方法を提供します。
- `````array
AsStringable::class
boolean
collection
date
datetime
immutable_date
immutable_datetime
decimal:<precision>
double
encrypted
encrypted:array
encrypted:collection
encrypted:object
float
hashed
integer
object
real
string
timestamp
属性キャスティングを示すために、is_admin
属性をキャストしましょう。これはデータベースに整数(0
または1
)として保存されていますが、ブール値にキャストします:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_admin' => 'boolean',
];
}
}
キャストを定義した後、is_admin
属性は、たとえ基礎となる値がデータベースに整数として保存されていても、アクセスする際に常にブール値にキャストされます:
$user = App\Models\User::find(1);
if ($user->is_admin) {
// ...
}
ランタイムで新しい一時的なキャストを追加する必要がある場合は、mergeCasts
メソッドを使用できます。これらのキャスト定義は、モデルにすでに定義されているキャストに追加されます:
$user->mergeCasts([
'is_admin' => 'integer',
'options' => 'object',
]);
<a name="stringable-casting"></a>
#### ストリングキャスト
モデル属性を[流暢な`````Illuminate\Support\Stringable`````オブジェクト](3b58d700a029d9f7.md#fluent-strings-method-list)にキャストするには、`````Illuminate\Database\Eloquent\Casts\AsStringable`````キャストクラスを使用できます:
``````php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'directory' => AsStringable::class,
];
}
}
`
配列およびJSONキャスト
``````php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'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;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsArrayObject::class,
];
}
同様に、LaravelはJSON属性をLaravelのCollectionインスタンスにキャストするAsCollection
キャストを提供します:
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::class,
];
}
``````php
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::using(OptionCollection::class),
];
}
`
日付キャスト
デフォルトでは、Eloquentはcreated_at
およびupdated_at
列をCarbonのインスタンスにキャストします。これはPHPのDateTime
クラスを拡張し、さまざまな便利なメソッドを提供します。追加の日付属性をキャストするには、モデルのcasts
メソッド内で追加の日付キャストを定義します。通常、日付はdatetime
またはimmutable_datetime
キャストタイプを使用してキャストする必要があります。
``````php
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime:Y-m-d',
];
}
`
列が日付としてキャストされると、対応するモデル属性値をUNIXタイムスタンプ、日付文字列(Y-m-d
)、日付時刻文字列、またはDateTime
/ Carbon
インスタンスに設定できます。日付の値は正しく変換され、データベースに保存されます。
モデルのすべての日付のデフォルトのシリアル化フォーマットをカスタマイズするには、モデルにserializeDate
メソッドを定義します。このメソッドは、データベースに保存するための日付のフォーマットには影響しません:
/**
* Prepare a date for array / JSON serialization.
*/
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format('Y-m-d');
}
モデルの日付をデータベースに実際に保存する際に使用するフォーマットを指定するには、モデルに$dateFormat
プロパティを定義する必要があります:
/**
* The storage format of the model's date columns.
*
* @var string
*/
protected $dateFormat = 'U';
日付キャスト、シリアル化、およびタイムゾーン
デフォルトでは、date
およびdatetime
キャストは、アプリケーションのtimezone
設定オプションで指定されたタイムゾーンに関係なく、日付をUTC ISO-8601日付文字列(YYYY-MM-DDTHH:MM:SS.uuuuuuZ
)にシリアライズします。このシリアル化フォーマットを常に使用し、アプリケーションのtimezone
設定オプションをデフォルトのUTC
値から変更せずに、アプリケーションの日付をUTCタイムゾーンに保存することを強くお勧めします。アプリケーション全体でUTCタイムゾーンを一貫して使用することで、PHPやJavaScriptで書かれた他の日時操作ライブラリとの最大の相互運用性が提供されます。
<a name="enum-casting"></a>
### 列挙型キャスト
Eloquentは、属性値をPHPの[列挙型](https://www.php.net/manual/en/language.enumerations.backed.php)にキャストすることも許可します。これを実現するには、モデルの`````casts`````メソッドでキャストしたい属性と列挙型を指定します:
``````php
use App\Enums\ServerStatus;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'status' => ServerStatus::class,
];
}
`
モデルでキャストを定義すると、指定された属性は、属性と対話する際に自動的に列挙型にキャストされます:
if ($server->status == ServerStatus::Provisioned) {
$server->status = ServerStatus::Ready;
$server->save();
}
列挙型の配列キャスト
時には、モデルが単一の列に列挙型の値の配列を保存する必要があります。これを実現するには、Laravelが提供するAsEnumArrayObject
またはAsEnumCollection
キャストを利用できます:
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'statuses' => AsEnumCollection::of(ServerStatus::class),
];
}
暗号化キャスト
暗号化されたテキストの最終的な長さは予測できず、平文の対応物よりも長くなるため、関連するデータベース列は`````TEXT`````型以上であることを確認してください。さらに、値がデータベースに暗号化されているため、暗号化された属性値をクエリまたは検索することはできません。
<a name="key-rotation"></a>
#### キーのローテーション
ご存知のように、Laravelはアプリケーションの`````app`````設定ファイルで指定された`````key`````設定値を使用して文字列を暗号化します。通常、この値は`````APP_KEY`````環境変数の値に対応します。アプリケーションの暗号化キーをローテーションする必要がある場合は、新しいキーを使用して暗号化された属性を手動で再暗号化する必要があります。
<a name="query-time-casting"></a>
### クエリ時のキャスティング
時には、クエリを実行する際にキャストを適用する必要があります。たとえば、テーブルから生の値を選択する場合などです。次のクエリを考えてみましょう:
``````php
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には、さまざまな組み込みの便利なキャストタイプがあります。ただし、時には独自のキャストタイプを定義する必要がある場合があります。キャストを作成するには、make:cast
Artisanコマンドを実行します。新しいキャストクラスは、app/Casts
ディレクトリに配置されます:
php artisan make:cast Json
すべてのカスタムキャストクラスは、CastsAttributes
インターフェースを実装します。このインターフェースを実装するクラスは、get
およびset
メソッドを定義する必要があります。get
メソッドは、データベースからの生の値をキャスト値に変換する責任があり、set
メソッドは、キャスト値をデータベースに保存できる生の値に変換する必要があります。例として、組み込みのjson
キャストタイプをカスタムキャストタイプとして再実装します:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class Json implements CastsAttributes
{
/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
* @return array<string, mixed>
*/
public function get(Model $model, string $key, mixed $value, array $attributes): array
{
return json_decode($value, true);
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return json_encode($value);
}
}
カスタムキャストタイプを定義したら、そのクラス名を使用してモデル属性にアタッチできます:
<?php
namespace App\Models;
use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => Json::class,
];
}
}
値オブジェクトキャスト
値をプリミティブ型にキャストすることに制限されることはありません。値をオブジェクトにキャストすることもできます。値をオブジェクトにキャストするカスタムキャストを定義することは、プリミティブ型にキャストするのと非常に似ています。ただし、set
メソッドは、モデルに生の保存可能な値を設定するために使用されるキー/値ペアの配列を返す必要があります。
例として、複数のモデル値を単一のAddress
値オブジェクトにキャストするカスタムキャストクラスを定義します。Address
値には、lineOne
とlineTwo
の2つの公開プロパティがあると仮定します:
<?php
namespace App\Casts;
use App\ValueObjects\Address as AddressValueObject;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
class Address implements CastsAttributes
{
/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject
{
return new AddressValueObject(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
* @return array<string, string>
*/
public function set(Model $model, string $key, mixed $value, array $attributes): array
{
if (! $value instanceof AddressValueObject) {
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();
値オブジェクトをJSONまたは配列にシリアライズするEloquentモデルを計画している場合は、値オブジェクトにIlluminate\Contracts\Support\Arrayable
およびJsonSerializable
インターフェースを実装する必要があります。
値オブジェクトキャッシング
値オブジェクトにキャストされた属性が解決されると、Eloquentによってキャッシュされます。したがって、属性が再度アクセスされると、同じオブジェクトインスタンスが返されます。
カスタムキャストクラスのオブジェクトキャッシング動作を無効にしたい場合は、カスタムキャストクラスにpublic withoutObjectCaching
プロパティを宣言できます:
class Address implements CastsAttributes
{
public bool $withoutObjectCaching = true;
// ...
}
配列/JSONシリアル化
EloquentモデルがtoArray
およびtoJson
メソッドを使用して配列またはJSONに変換されると、カスタムキャスト値オブジェクトは通常、Illuminate\Contracts\Support\Arrayable
およびJsonSerializable
インターフェースを実装している限り、シリアライズされます。ただし、サードパーティライブラリによって提供される値オブジェクトを使用する場合、これらのインターフェースをオブジェクトに追加する能力がない場合があります。
そのため、カスタムキャストクラスが値オブジェクトのシリアル化を担当することを指定できます。これを行うには、カスタムキャストクラスはIlluminate\Contracts\Database\Eloquent\SerializesCastableAttributes
インターフェースを実装する必要があります。このインターフェースは、クラスが値オブジェクトのシリアル化形式を返すserialize
メソッドを含む必要があることを示します:
/**
* Get the serialized representation of the value.
*
* @param array<string, mixed> $attributes
*/
public function serialize(Model $model, string $key, mixed $value, array $attributes): string
{
return (string) $value;
}
インバウンドキャスティング
時には、モデルに設定されている値のみを変換し、モデルから属性を取得する際には操作を行わないカスタムキャストクラスを書く必要があります。
インバウンド専用のカスタムキャストは、CastsInboundAttributes
インターフェースを実装する必要があります。これは、set
メソッドを定義するだけで済みます。インバウンド専用のキャストクラスを生成するには、make:cast
Artisanコマンドを--inbound
オプションで呼び出すことができます:
php artisan make:cast Hash --inbound
インバウンド専用キャストの古典的な例は「ハッシュ化」キャストです。たとえば、指定されたアルゴリズムを介してインバウンド値をハッシュ化するキャストを定義できます:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;
class Hash implements CastsInboundAttributes
{
/**
* Create a new cast class instance.
*/
public function __construct(
protected string|null $algorithm = null,
) {}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return is_null($this->algorithm)
? bcrypt($value)
: hash($this->algorithm, $value);
}
}
キャストパラメータ
カスタムキャストをモデルにアタッチする際、キャストパラメータはクラス名と:
文字を使用して区切り、複数のパラメータをカンマで区切ることによって指定できます。パラメータはキャストクラスのコンストラクタに渡されます:
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'secret' => Hash::class.':sha256',
];
}
キャスト可能なもの
アプリケーションの値オブジェクトが独自のカスタムキャストクラスを定義できるようにしたい場合があります。カスタムキャストクラスをモデルにアタッチする代わりに、Illuminate\Contracts\Database\Eloquent\Castable
インターフェースを実装する値オブジェクトクラスをアタッチすることもできます:
use App\ValueObjects\Address;
protected function casts(): array
{
return [
'address' => Address::class,
];
}
``````php
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;
class Address implements Castable
{
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): string
{
return AddressCast::class;
}
}
`
``````php
use App\ValueObjects\Address;
protected function casts(): array
{
return [
'address' => Address::class.':argument',
];
}
`
キャスト可能なものと匿名キャストクラス
「キャスト可能なもの」とPHPの匿名クラスを組み合わせることで、値オブジェクトとそのキャスティングロジックを単一のキャスト可能なオブジェクトとして定義できます。これを実現するには、値オブジェクトのcastUsing
メソッドから匿名クラスを返します。匿名クラスはCastsAttributes
インターフェースを実装する必要があります:
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Address implements Castable
{
// ...
/**
* Get the caster class to use when casting from / to this cast target.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): CastsAttributes
{
return new class implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): Address
{
return new Address(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
public function set(Model $model, string $key, mixed $value, array $attributes): array
{
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
};
}
}