🤔

Laravelでマイグレーション通りの型じゃなかった話

に公開

自分は今までlaravelを利用していた時にDBから取得しているものはマイグレーションファイルに設定してある型と同じだと思っていたが実は違いました
そのことについてまとめます

マイグレーションと実際の取得型の違い

型を定義する

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->integer('price');
        $table->boolean('purchase_enabled')->default(true);
        $table->dateTime('purchase_at');
        $table->timestamps();
    });
}

商品のテーブルを用意してみました。
商品名はstring、価格はinteger、購入状況はboolean、購入した時間はdatetimeです。
以下のようにコントローラーで全件取得し、テーブル形式で返します。

public function index()
{
    $products = Product::all();
    return view('index',compact('products'));
}
<table border="2">
    <h2>税抜き価格</h2>
    <thead>
        <th>商品名</th>
        <th>価格</th>
        <th>購入状況</th>
        <th>購入した時間</th>
    </thead>
    @foreach ($products as $product)
        <tr>
            <td>{{$product->name}}</td>
            <td>{{$product->price}}</td>
            <td>{{$product->purchase_enabled ? '購入可' : '在庫なし'}}</td>
            <td>{{$product->purchase_at}}</td>
        </tr>
    @endforeach
</table>
<h2>税込価格</h2>
<table border="2">
    <thead>
        <th>商品名</th>
        <th>価格</th>
        <th>購入状況</th>
        <th>購入した時間</th>
    </thead>
    @foreach ($products as $product)
        <tr>
            <td>{{$product->name}}</td>
            <td>{{$product->price * 1.1}}</td>
            <td>{{$product->purchase_enabled ? '購入可' : '在庫なし'}}</td>
            <td>{{$product->purchase_at}}</td>
        </tr>
    @endforeach
</table>

これで次のような表が表示されます。

上の画像を見るに{{$product->price * 1.1}}{{$product->purchase_enabled ? '購入可' : '在庫なし'}}などが機能しているので一見intやboolなどそれぞれのカラムはマイグレーションファイルの設定と同じような型だと思えます。

型をチェックしてみる

それではvar_dumpをつかって一つ一つの詳細に見てきます。

public function index()
{
    $products = Product::first();
    var_dump($products->name);
    var_dump($products->price);
    var_dump($products->purchase_enabled);
    var_dump($products->purchase_at);

    return view('index',compact('products'));
}

こうすると以下のように出力されました。

booleanはintでdatetimeはstringになっていました。
なので例えば{{$product->purchase_enabled ? '購入可' : '在庫なし'}}の部分も{{$product->purchase_enabled === true ? '購入可' : '在庫なし'}}のようにしてしまうとすべて在庫なしになってしまいます。
これは、boolean型と厳密比較した際に、DBから返されるのが int(1)true と一致しないためです。
また、datetimeをformatで変換しようとすると以下のようにエラーとなってしまいます。

これはdatetimeはDBから取得した際にstring型になっているので変換できず、エラーになります。

型を変更する(casts)

このままだと使えないので以下のコードをモデルに追記します。

models/Product.php
protected $casts = [
    'purchase_enabled' => 'bool',
    'purchase_at' => 'datetime',
];

モデルに$castsを追加することでカラムの型を変更して取得することができます。
castsの詳しい情報は以下
https://laravel.com/docs/12.x/eloquent-mutators#attribute-casting

変更を確認

var_dumpで調べるとbool値、datetimeになっていることが分かります。

また、以下のようにフォーマットや厳密演算子に対応していることが分かります。

これで型を変更して実装することができましたが、一つ注意点があります。

Eloquentとクエリビルダ

LaravelではDBからデータを取得する方法としてEloquentとクエリビルダの2つがあります。

public function index()
{
    $products = Product::all(); // Eloquent
    $products = DB::table('products')->get(); // クエリビルダ

    return view('index',compact('products'));
}

Eloquentとクエリビルダについてはどちらもメリット、デメリットがあるので使い分けにはなりますが、castsはEloquentの機能なのでクエリビルダで同じようにモデルに記載してもエラーのままになってしまいます。
なのでcastsで型変換したい場合はEloquentで取得するようにしてください。

終わり

以上です。

Discussion