Posted on

Attributeを怖がらないで欲しいなって

「ビューテンプレートに計算式を書こうとしたなら、その計算式をモデルに置けないかと考える」

そこまで変なことを言ってるつもりはないです。たぶん。
少なくとも、ビューテンプレートごとに同じ計算式をコピペし続けるよりははるかにましだと思うんです。
最近はその考え方も普及したのか、ビューテンプレートに計算式をあまり見なくなりました。よきこと。

ただ、ビューを避ければいいってもんでもなくて、リポジトリとかユースケースで
・EloquentモデルをtoArray展開して
・各レコードにその場で計算結果を追加し
・その計算結果を追加した配列を返す

みたいなことをしているのはよく見ます。子モデル集計とかが影響しているならなおさら。
こういう集計を結構よく見かけるのに、結局そのあたりで同じコードがコピペされまくっとるんです。
ちょっとずつ他のこともしているから単純なコピペでなくて、同じ集計部分が含まれているとかそういう、リファクタリングが絶妙にしにくいやつ。

そういうのに名前を付けて、その部分だけ切り出すのがEloquentにあるAttributeという切り口でして。
toArray展開してしまうと使えなくなるんで注意ですよ。

大体見たらわかると思います。

    //Models/Order.php 発注書モデル
    use Illuminate\Database\Eloquent\Casts\Attribute;
    use Illuminate\Support\Facades\Hash;

    // hasMany OrderDetails(注文明細)

    /**
     * 合計点数(注文明細の数量合計)
     * $order->item_count で読めるようになります。
     */
    protected function itemCount():Attribute
    {
        //明細をイーガーロードしときます
        $this->load('OrderDetails');

        //get は何を計算してこのフィールドに置くか決める
        return Attribute::make(
            get:function(){
                return $this->OrderDetails->sum('quantity');
            }
        );
    }

   /**
    * 備考欄の前後空白をトリム
    * $this->trim_notes = 備考文字列 で、備考文字列前後の空白を削り、タグを剝いだ状態にして$this->notesに保存
    * 他のところで同様のものを作成している場合があるので、意外とset側は使わないがち。便利な事例求む。
    */
    protected function trimNotes():Attribute
    {
        //setは何を計算してどのフィールドに置くか決め、配列を返す
        return Attribute::make(
            set:function(string $value){
                return [
                    'notes' => strip_tags($value)
                ];
            }
        );
    }

laravelのドキュメントには直接カラム名と同じアクセサを使うような事例が多く載ってて、使えないことはないけど
チームで開発していて、知らないうちにアクセサが挟まってたみたいなことになったら面倒なので、奥進では今のところ「別名で作れ」が大前提になっております。
DBには存在しないカラム名だけど、こういうアクセサ経由で読み書きが一か所にまとまるんです。
setの時は必ず配列で返すんです(値だけを返すとベースのカラム名と同じ扱いになるので…)
複数カラムにまたがる場合も配列で返せばヨシです(その例はそのままReadoubleに載ってるのでそちら見て…)

あと、メソッド名と実際の呼び出し名が違うことに注意してくださいね。結構ここハマリポイントです。メソッド名はitemCountで読むときは$order->item_countという具合です。

いっこだけ、上司に怒られた使い方あります。
「このアクセサ使うと毎回DBにアクセスして動作が重くなるから使わんでくれ」とのこと。
中でがっつりクエリ発行してたやつで、本末転倒になっていました。
できるなら、この中に書くのはクエリ発行しない方法での実装がいいですよ。
Eloquentの子モデルに対するコレクション操作とかもイーガーロードが吉です。