2児のパパ系Webエンジニア
の開発日記
A Dad-of-Two Engineer's Development Diary

Laravel 13のPHP属性でモデルとジョブの設定がスッキリ!#[Table]・#[Fillable]の書き方

Laravel 13のPHP属性でモデルとジョブの設定がスッキリ!#[Table]・#[Fillable]の書き方

🙌 結論から

Laravel 13 から、モデルやジョブの設定を PHP の属性(#[...])でクラスの頭に書けるようになりました🎉

protected $table や protected $fillable がズラッと並んでいたところが、クラス宣言の上に数行まとまる感じです。

クラスを開いた瞬間に「このモデルの設定」が目に入るのが、思っていた以上に快適でした(^o^)

ただし、親クラスで属性、子クラスでプロパティ、と混ぜるとハマります(・_・;)

ここだけ気をつければ、既存のプロパティ書きとも共存できるので、少しずつ移せます👍

💡 モデルのプロパティを属性にしてみた

仕事で触っている案件に、テーブル名も主キーもLaravelの規約から外れているモデルがありました( .. )

これまではこんな感じです。

class Order extends Model
{
    protected $table = 'trn_orders';
    protected $primaryKey = 'order_code';
    protected $keyType = 'string';
    public $incrementing = false;
    protected $fillable = ['order_code', 'customer_id', 'total'];
}

Laravel 13 だと、こう書けます!

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table('trn_orders', key: 'order_code', keyType: 'string', incrementing: false)]
#[Fillable(['order_code', 'customer_id', 'total'])]
class Order extends Model
{
}

#[Table] 1つで、テーブル名・主キー・キーの型・自動採番の有無までまとめて指定できます。

プロパティ4つが1行になったので、クラスの中身はリレーションとスコープだけになりました😊

正直、ここまで読みやすくなるとは思ってなかったです・・・(・∀・)

プロパティと属性は混在してもちゃんと解決してくれるので、新しく触るモデルから順番に移しています。

👀 ジョブの設定も属性で書ける

キューのジョブも同じように書けます。

use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;

#[Tries(3)]
#[Timeout(120)]
class SendInvoiceMail implements ShouldQueue
{
    // handle() だけに集中できる
}

リトライ回数やタイムアウトって、障害のときに一番最初に確認したい値なんですよね。

それがクラスの一番上にあるので、ログを見ながらジョブを開いたときに探さなくて済むようになりました!

これはかなり便利です🔥

#[Backoff] や #[UniqueFor] なども同じ形で書けるので、ジョブの設定はほぼ全部クラスの頭に集まります。

🔥 親クラスの属性でハマった

一番つまずいたのがここです😭

うちでは外部 API を叩くジョブ用に基底クラスを作っていて、そこに #[Timeout(40)] を付けていました。

で、重いバッチだけ子クラスで public int $timeout = 1700; と上書きしたところ・・・なぜか40秒で落ちる(´・ω・`)

調べてみると、親クラスの属性が子クラスのプロパティより優先されてしまう挙動でした(T_T)

同じ仕組みで #[Tries] #[Backoff] #[MaxExceptions] #[UniqueFor] も影響を受けます。

Laravel 側でも修正の PR(laravel/framework #60369)がマージされていて、13.x の新しいリリースでは子クラスのプロパティが優先されるようになっています。

とはいえ、バージョンを上げるまでの回避策としては、子クラスでも属性で上書きするのが確実でした。

#[Timeout(1700)]
class ImportLargeCsv extends BaseApiJob
{
}

私は「属性とプロパティをクラス階層の中で混ぜない」をチームのルールにしました!

これで同じ事故はもう起きないはずです( `・ω・´)

🔧 既存プロジェクトで書き換えるときの進め方

Laravel 13 に上げたからといって、全部のモデルを一気に属性へ書き換える必要はありません(^^)

プロパティで書いた設定もそのまま動くので、私ならこんな順番で進めます。

  1. 新しく作るモデル・ジョブから属性で書く: 既存のコードには触らない
  2. 触ったついでに書き換える: 機能追加や不具合修正でそのモデルを開いたときに、同じプルリクで属性に移す
  3. 継承しているクラスは後回し: 親クラスにプロパティがあるモデルは、記事で書いたように挙動の確認が要るので最後にする

書き換えたあとは、動作が変わっていないかを確認するのが大事です👀

php artisan tinker --execute="dump((new App\Models\Order)->getTable(), (new App\Models\Order)->getFillable());"

こんなふうに getTable() や getFillable() の結果を、書き換える前と後で見比べておくと安心です。

チームで使うなら、**「新規は属性、既存は触ったときだけ」**というルールを決めておくと、プロパティと属性が混ざっても迷わずに済みます✨

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

👍 まとめ

Laravel 13 の PHP 属性は、設定値をクラスの頭に集めて、中身をロジックだけにできるのが一番の良さでした😆

#[Table] はモデルの規約外の設定をまとめて書けますし、ジョブの #[Tries] #[Timeout] は障害対応のときに探す手間が減ります。

一方で、親クラスに属性、子クラスにプロパティ、という混ぜ方は避けたほうが安全です!

既存コードとは共存できるので、触るファイルから少しずつ置き換えていくのがおすすめです🙏

ちなみに Laravel 13 は PHP 8.3 以上が必要なので、アップグレード前に PHP のバージョンも確認しておきましょう!

※ バージョン番号・料金・機能は今後も変わる可能性があります。最新情報は公式サイトをご確認ください👀

参考になったらシェアしてもらえるとうれしいです🙌

Xでポスト Threads はてブ LINE