11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Laravel]$casts => datetime(date):~の落とし穴

Posted at

はじめに

新プロジェクトでのタスクでプロジェクト内のModelを一斉作成していた時に気付いたこと。

初めてModelを1から作成しており、$hidden$fillable, $guardedなどいろんなプロパティがあって便利だなあと思っていました。

その中でも属性の$castsがちょっと直感的な挙動とは違った挙動だったのでメモ。

前提

  • Windows Home 10

  • PHP 8.0.7

  • Laravel 8.5.0

どんな落とし穴?

具体例で記載。

やりたいこと

Userテーブルにあるemail_verified_atというカラムの値を「YYYY年MM月DD日」というフォーマットで出力したい

調べたこと

ドキュメントによると

アクセサ、ミューテタ、および属性キャストを使用すると、Eloquentモデルインスタンスで属性値を取得または設定するときに、それらの属性値を変換できます。

さらに

属性キャストは、モデルで追加のメソッドを定義することなく、アクセサやミューテタと同様の機能を提供します。

なので自分は

「first()とかでdatetime型のカラム(今回ならemail_verified_at)を取得した時点でキャストにより、型変換及びフォーマット変更されるんだな」

と理解したので、意気揚々と実装を進めた。

テーブル構成

  • Userテーブル
カラム名 制御 説明
id int PK ユーザーID
name string not null 名前
... ... ... ...
email_verified_at datetime アプリからの確認メールをユーザーが確認した日時
「YYYY年MM月DD日」としたい

今回のキャストするターゲットのemail_verified_atが登録されている

ソースコード

Route

適当にアクセスできるように実装しています。(URL決めるのが手間&記事の文字数増えるので割愛)

Model

class User extends Model
{
    use HasFactory

    /**
     * related table.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime:YYYY年MM月DD日',
    ];

		...
}

今回の目玉の$casts。これで想定通りのフォーマットで取得できる(と思っていた)

Controller

一先ず、簡単にddしてみる

<?php

namespace App\Http\Controllers\Mypage;

use App\Http\Controllers\Controller;
use App\Models\User;

class IndexController extends Controller
{
    /**
     * Index interface.
     *
     */
    public function index()
    {
        $user = User::first(); // 一番最初のユーザー取得
		dd($user->email_verified_at);
    }
}

結果

↓想定値

'2022年04月13日'

↓実際値

'2022-04-13 07:03:31' // 変化なし

あれ。フォーマットされてない。。。

設定ミスったか?と思い、試しにintegerでキャストしてみると

	/**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'integer',
    ];

結果↓

2022

なんだかintegerへのキャストはうまくいっているらしい。(2022が表示されたのは2022年だからかな?)

なぜうまくいかないのか原因調査してみた。

結論

しっかりとドキュメント見返すと答えが書かれていた

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

なんだ。そういうことか。要は

$castsは他の型(integerとかstring)への変換と違って、first()とかfind()で取得したタイミングで実行されるわけではなく、toArray()とかtoJson()したタイミングで実行される」

ということでした。

なので当初のやりたいこと(YYYY年MM月DD日の出力)に戻ると

    ...

	/**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime:YYYY年MM月DD日',
    ];

		...

こうして、

$user = User::first();

dump($user->email_verified_at); // ここではDBの値のまま
>>> 2022-04-13 07:03:31

dump($user->toArray());
>>>
array:5 [
  "id" => 1
	...
  "email_verified_at" => "2022年04月13日" // cast&フォーマットされる!
	...
]

こんな感じでtoArraytoJsonなどシリアライズするタイミングで変換される、ということでした

おわりに

  • なぜdate, datetimeだけ他の型に比べて、$castsが実行されるタイミングが違う仕様になっているのか、までは追及できませんでした。
    • 妥協的仕様(Carbonが返ってくるから仕方ない的な)なのか、利便性重視的仕様(この仕様のほうが多くのユースケースにおいて便利、的な)なのかくらいは知りたかった
    • 個人的には実際にハマっているので前者であってほしいと思うばかり...
  • とはいえ、Eloquentモデルインスタンスの取得時のキャストやフォーマット変更が普段の開発の意識外で可能であることが分かり、今後の想定外の挙動に対応する手助けになるはずなのでドキュメントを深読みする機会が得れてよかった。
  • $castsの中でサポートしているキャストタイプの中でも immutable_dateimmutable_datetimeという見慣れないモノも発見したのでこれは別途記事でまとめます。(Carbon関係の話ですが)

最後まで読んでいただきありがとうございました!

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?