1
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?

PR: フリーランスコネクト株式会社
フリーランスコネクト採用ページ

【2026年8月】Laravel/PHP 今月のまとめ|13.24→13.27、画像APIの完成とキュー運用、AI SDK の可観測性

1
Posted at

2026年8月の Laravel News を1本にまとめた確定版だ。先月の Laracon US 2026 で発表されたものが実際にリリースされ始めた月で、artisan doctor、laravel/head、公式 Zed 拡張が出そろった。コアは 13.24 から 13.27 の4リリース。

新着をすべて並べると埋もれるので、コアの変更点はコード付きで詳しく扱い、パッケージはテーマ別に厳選、残りは末尾にリンクでまとめた。今月の周辺パッケージは AI の可観測性とガードレールに寄っていて、公式 AI SDK にライフサイクルイベントが入り、AIエージェントをアプリの中で走らせるパッケージが2つ出た。

出典はすべて Laravel News の各記事。バージョン番号やメソッド名は原文に合わせている。

TL;DR

  • コアは4リリース(13.24→13.27)。13.20 で入った画像APIが dominantColor()、HEIC対応、HTTPレスポンス化で一通り完成した。
  • キュー運用の道具が厚い。queue:pause --all で全キューを一括停止、Queue::forward() でジョブクラスに触らず流し先を変更、JobReleased と UniqueJobSkipped で今まで見えなかった挙動が見えるようになった。
  • 本番事故を潰す小さな追加が続いた。例外メッセージからバインディング値を消す mask_bindings_in_exception_messages、悲観ロック付きリロードの refreshForUpdate()、大文字と小文字を区別する whereBinary()。
  • Laracon 発のファーストパーティが出そろった。artisan doctor(診断)、laravel/head(メタタグ)、公式 Zed 拡張。
  • AIは可観測性の層へ。AI SDK 0.11 が実行IDとステップ単位のイベントを持ち、ツール定義を遅延ロードする ToolSearch も入った。アプリ内でコーディングエージェントを走らせる Laravel Tackle と、監査を任せる Laravel Auditor も登場。
  • エコシステムでは NativePHP v4 が Blade から本物の SwiftUI / Compose を描くようになり、Linux/macOS 向けの無料 Herd 代替 Lerd が出た。

Laravel コアの進化(13.24 → 13.27)

13.24:画像の主要色とHEIC、ワイルドカードバリデーションの高速化

13.20 で入った画像APIに、画像の主要色を返す dominantColor() が加わった。画像を1ピクセルまで縮小してその色を16進文字列で返すもので、写真の読み込み中に敷くプレースホルダの背景や、アートワークの色でカードを染める用途に使える。

use Illuminate\Support\Facades\Image;

$color = Image::fromPath(storage_path('app/photo.jpg'))->dominantColor(); // "#8a6f4c"

同じ色を背景値としても渡せる。contain() や rotate() に dominant を指定すると、レターボックスの帯や回転で空いた角を、あらかじめ決めた色ではなく画像自身の色で埋める。結果は画像インスタンスごとにメモ化され、保留中の変換があれば先に適用してから採色するので、保存しようとしている画像の色が取れる。

Image::fromUpload($request->file('photo'))
    ->contain(1200, 800, background: 'dominant')
    ->store('photos');

入力側では AVIF と HEIC/HEIF を受け付けるようになった。出力に toHeic() と optimize('heic') が加わり、image バリデーションルールも3形式を認識する。iPhone は既定で HEIC を書き出すので、これまではフレームワークに届く前に変換しておく必要があった。ただし HEIC の処理には HEIC コーデック入りの Imagick ビルドが要る。

Image::fromPath(storage_path('app/photo.heic'))
    ->usingImagick()
    ->cover(1200, 800)
    ->toAvif()
    ->quality(80)
    ->store('photos');

Eloquent のクエリビルダに modelKeys() が入った。コレクションには前からあったが、クエリから直接同じ配列が欲しいときは pluck('id') で主キー名を自分で書く必要があった。修飾済みのキー名を引くので、$primaryKey を変えたモデルや JOIN したクエリでもカラム名を決め打ちしなくて済む。

$ids = Post::query()->where('published', true)->modelKeys(); // [1, 2, 3]

新しい array_keys バリデーションルールは、「このキー以外は入れるな」だけを答える。Rule::array(['sort', 'direction']) でも同じ拒否はできるが、失敗メッセージが「配列である必要があります」という汎用文になって、2つのチェックのどちらで落ちたか読み手に分からなかった。

$request->validate([
    'options' => Rule::arrayKeys(['sort', 'direction']),
]);

// 文字列でも書ける
$request->validate([
    'options' => 'array_keys:sort,direction',
]);

['sort' => 'name', 'colour' => 'red'] を渡すと「The options field must only contain the following keys: sort, direction.」になる。カスタムメッセージでは :values(許可キー)と :unexpected(実際に落とした原因のキー)が使え、APIレスポンスでは後者のほうが実用的だ。キーは「許可」であって「必須」ではないので、全部そろえろという意味にはならない(そちらは required_array_keys のまま)。

地味だが効くのが、ワイルドカードバリデーションの性能修正だ。foo.*.bar の展開が要素数に対して二乗で効いていて、Validator::make() の中、1つもルールが走る前に時間を使い切っていた。PRの計測値は次のとおり(ワイルドカード1つの下にルール17個)。

要素数 修正前 修正後
1,000 0.98s 0.11s
4,000 18.59s 0.47s
8,000 85.13s 0.98s

8,000件でもペイロードは約1.1MBしかないので、post_max_size やWebサーバーのボディ制限には引っかからない。配列に max:500 を付けても効かなかった。展開のほうが先に走るからだ。

ほかに Arr::forget() が別の要素を消していたバグの修正(ドット記法のキーを処理したあと内部参照がネストした配列を指したままになり、次のキーがそこに解決されていた)、キャッシュ済みルートでの404が2,000ルートに対して71msから2.5msになるルックアップの索引化などが入っている。

出典:https://laravel-news.com/laravel-13-24-0 / 個別記事:主要色の抽出、HEICの検証と変換、array_keys ルール

13.25:全キューの一括停止と artisan dev のタブUI

個別のキュー停止は前からできたが、デプロイのときにはその粒度が邪魔になる。ワーカーが十数個、名前付きキューが複数となると、接続とキュー名の組を全部指定して回ることになり、しかもその名前は機能追加のたびに変わる。かといってメンテナンスモードに逃げると、ワーカーを止めたいだけなのにサイト全体が落ちる。

php artisan queue:pause --all
php artisan queue:resume --all
use Illuminate\Support\Facades\Queue;

Queue::pauseAll();
Queue::resumeAll();

グローバルスイッチはキャッシュキー1つで、ワーカーはキュー個別のキーと並べて見る。両者は独立していて、resumeAll() はグローバルフラグを消すだけで、個別に止めたものは止まったままにする。デプロイスクリプトが、誰かが意図的に止めたキューを勝手に再開させないための設計だ。QueuesPaused と QueuesResumed のイベントも入った。

artisan dev の中身が concurrently から @laravel/multiplex に替わった。Vite のリビルドとキューワーカーと Pail が同じ画面に流れ込む状態から、プロセスごとのタブ、検索、個別再起動、クラッシュ時の自動再起動があるターミナルUIになる。モードは tabs(既定)、stream(従来どおり1本の流れ)、inline(TTYが無いとき自動)の3つ。

php artisan dev --stream
php artisan dev --timestamps --no-restart
use Illuminate\Foundation\DevCommands;

DevCommands::stream();
DevCommands::withTimestamps();
DevCommands::disableAutoRestart();
DevCommands::bufferSize(5000);

Windows は concurrently にフォールバックする(multiplex は現状 macOS と Linux のみ)。新経路の Node 下限は v22.13。

画像APIが Responsable を実装した。13.25 からは、ルートから画像インスタンスをそのまま返せる。toResponse() は処理後のバイト列と、出力から読んだ Content-Type を返すので、ヘッダは元ファイルではなくパイプラインが実際に作った形式に一致する。

use Illuminate\Support\Facades\Image;

Route::get('/avatars/{user}', function (User $user) {
    return Image::fromStorage($user->avatar_path)
        ->cover(200, 200)
        ->toWebp()
        ->quality(80);
});

キューの可観測性まわりも増えた。ShouldBeUnique のロックが取れずに破棄されたジョブは今まで痕跡なく消えていて、「一意性がちゃんと効いている」のか「ロックが解放されていない」のかを区別できなかった。UniqueJobSkipped がそれを報告する。JobTimedOut には超過秒数を持つ $timeout が加わり、ジョブ自身のタイムアウトとワーカー側(queue:work --timeout=120)のどちらで落ちたかが分かる。通知も #[FailOnTimeout] を読む。

ほかに、複数のクッキーを一度に失効させる withoutCookies(['session', 'tracking', 'preferences'])、foreignUlidFor() スキーマヘルパ、そして Request::all() で入力とファイルがキー衝突したときに入力を優先する変更(従来は UploadedFile が勝っていた。file() の挙動は変わらない)が入った。

出典:https://laravel-news.com/laravel-13-25-0 / 個別記事:デプロイ中の全キュー停止、artisan dev コマンド、artisan dev のターミナルUI、ルートから画像を返す

13.26:read-through ディスクとリスナーのデバウンス

read-through ファイルシステムドライバは、既存のディスク2つを1つに合成する。読み込みはまずプライマリを見て、無ければフォールバックへ落ち、その途中でプライマリにコピーする。つまり実際にリクエストされたファイルだけでプライマリが遅延的に埋まっていく。書き込みと削除とディレクトリ一覧はプライマリのみ。

'assets' => [
    'driver' => 'read-through',
    'primary' => 'r2',
    'fallback' => 'legacy-s3',
],

copy => false を付けると昇格せずにフォールバックから配信するだけになり、本番のファイルを向いた開発環境に向く。昇格の失敗は既定で握りつぶされる(コピーに失敗したせいで読み込みが壊れないように)。表に出したければ throw_on_promotion_failure => true。

13.6 でキュージョブに入った #[DebounceFor] が、キュー投入されるイベントリスナーにも付けられる。同じイベントが連続で飛んだとき、ウィンドウ内の最後の1回だけがリスナーを走らせる。

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
    public function debounceId(ProductUpdated $event): string
    {
        return (string) $event->product->getKey();
    }

    public function handle(ProductUpdated $event): void
    {
        // 最新の状態で1回だけ再インデックス
    }
}

debounceId() でリソース単位にウィンドウを切り、maxWait で「忙しいイベント列がいつまでも先送りされる」上限を決める。デバウンスはそのリスナーだけに効くので、同じイベントの他のリスナーは毎回走る。ShouldBeUnique との併用は LogicException になる(一意性は最初の1回を残し、デバウンスは最後の1回を残す。方向が逆だ)。

Queue::forward() は、あるキューに投げられたものを別のキューや別の接続へ、サービスプロバイダ1箇所で流し替える。ジョブクラスにもディスパッチ箇所にも触らない。

use Illuminate\Support\Facades\Queue;

Queue::forward('reports', 'reports.fifo', 'cloud'); // 名前を変えて接続も移す
Queue::forward('payments', connection: 'cloud');    // 名前はそのまま接続だけ移す
Queue::forward('updates', 'notifications');         // 同じ接続で名前だけ変える

Queue::forward([
    'reports' => 'reports.fifo',
    'emails' => 'emails.fifo',
], connection: 'cloud');

Process まわりも3点。プロセスプールと結果が IteratorAggregate になった(従来 ProcessPoolResults を foreach すると public プロパティを回していて、終了コードを見るつもりのループが何も見ていなかった)。アイドルタイムアウトが専用の ProcessIdleTimedOutException を投げるようになり、「ハングした」と「遅いだけ」を投げ分けられる。フェイクには assertRanCount() と assertRanInOrder() と recorded() が付いた。

try {
    Process::timeout(600)->idleTimeout(30)->run('./deploy.sh');
} catch (ProcessIdleTimedOutException $e) {
    // 30秒出力なし。kill して通知する
} catch (ProcessTimedOutException $e) {
    // 出力は出ている。遅いだけなので時間を延ばして再試行
}

Eloquent 側では wherePivot() がクロージャを受け取れるようになり、カスタム中間テーブルモデルに定義したスコープをリレーション経由で呼べる。inOrderOf() は enum を受け付ける。

$project->subscribers()
    ->wherePivot(fn ($query) => $query->active()->notMuted())
    ->get();

$orders = Order::query()
    ->inOrderOf('status', [Status::Pending, Status::Processing, Status::Shipped])
    ->get();

可観測性の面では JobReleased イベントが加わった。WithoutOverlapping のようなミドルウェア経由でジョブがキューに戻された場合、従来の JobReleasedAfterException は例外起因しか拾わないので見えていなかった。あわせてワーカーが、消費しているキューが停止中であることを出力するようになった(停止中のワーカーが単に無言になるのは、ハングと区別がつかなかった)。Guzzle 8 対応と Redis クラスタまわりの堅牢化も入っている。

出典:https://laravel-news.com/laravel-13-26-0 / 個別記事:read-through ファイルシステム、Queue::forward()、デバウンスされたキューリスナー

13.27:例外メッセージのバインディング隠蔽と whereBinary()

今月いちばん実務に効くのはこれだと思う。QueryException はメッセージにバインディング値を埋め込むので、insert が失敗すると束縛した値が全部 getMessage() に載る。そしてそのメッセージは例外が行くところ全部、failed_jobs テーブル、ログファイル、APMのスパンに流れていく。メールアドレスや氏名がそのまま記録される。

'mysql' => [
    'driver' => 'mysql',
    // ...
    'mask_bindings_in_exception_messages' => env('DB_MASK_BINDINGS', false),
],

有効にすると ? プレースホルダのまま残る。

// 既定
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (foo@example.com))

// マスク後
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (?))

キーは config/database.php に同梱されるので、設定ファイルを publish していないアプリでも DB_MASK_BINDINGS=true で入れられる。既定は false。変わるのはメッセージだけで、getBindings() は値を返し続ける。

whereBinary() は MySQL/MariaDB でバイト単位の比較をする。MySQL の既定照合順序は大文字と小文字を区別しないので、where('name', 'John') は john にも JOHN にも当たる。今までは生SQLに落ちるしかなかった。

DB::table('queues')->whereBinary('name', $queueName)->first();
// select * from `queues` where `name` = binary ?

DB::table('queues')->whereNotBinary('name', $queueName)->get();
// select * from `queues` where `name` != binary ?

orWhereBinary() と orWhereNotBinary() も入っている。Postgres、SQLite、SQL Server は RuntimeException を投げる(Postgres と SQLite はもともと大文字と小文字を区別して比較する。非対応エンジンで例外にするのは whereLike() と同じ方針だ)。

refreshForUpdate() は、手元にあるモデルインスタンスを悲観ロック付きで読み直す。ルートモデルバインディングやジョブのペイロードから解決したモデルにロックを掛けるには、そのインスタンスを捨てて主キーで引き直す必要があった。

DB::transaction(function () use ($product) {
    $product->refreshForUpdate();

    if ($product->stock === 0) {
        throw new RuntimeException('The product is out of stock.');
    }

    $product->decrement('stock');
});

ロックはトランザクションの生存期間しか保たないので、呼び出しはトランザクションの中に置く。在庫のように、読んでから書き戻すまでのあいだに別の更新が割り込む余地をなくす用途だ。

ほかに Cloud ファサード(Cloud::hosted() / usesManagedQueues() / queue())、全キュー合計を1回で取る Queue::totalPendingSize() などの3メソッド、MariaDB 11.7+ でのベクトル距離クエリ、Postgres の keepalive DSN オプション、SQS の AWS 認証情報キャッシュ共有が入った。

入力とバリデーションの堅牢化もまとまって入っている。Request::merge(['*' => 226]) が入力配列を丸ごと吹き飛ばしていた問題(data_set() が * をワイルドカードとして扱っていた)、リテラルのドットを含むキーが未知フィールド検査をすり抜けて validated() から消えていた問題、in_array が "1e0" と "1" を緩い比較で一致させていた問題などだ。

なお、13.26 で入った orWhereKey() / orWhereKeyNot() は whereKey() のシグネチャを変えて Builder のサブクラスを壊したため 13.26.1 で差し戻され、13.27 で入れ直された。

出典:https://laravel-news.com/laravel-13-27-0 / 個別記事:例外メッセージのバインディングをマスクする、whereBinary()、refreshForUpdate() による悲観ロック

コアの早見表

バージョン 目玉 種別
13.24 dominantColor() / HEIC入出力 / modelKeys() / array_keys ルール / ワイルドカードバリデーションの高速化 機能追加+性能
13.25 queue:pause --all / artisan dev のタブUI / 画像の Responsable 化 運用+開発体験
13.26 read-through ディスク / リスナーの #[DebounceFor] / Queue::forward() 運用
13.27 例外メッセージのバインディングマスク / whereBinary() / refreshForUpdate() セキュリティ+データ整合

Laracon US 2026 発のファーストパーティ3本

7月末の Laracon で発表されたものが、8月頭にまとめてリリースされた。

Laravel Doctor は artisan doctor で健全性チェックを走らせる公式パッケージだ。APP_KEY は設定されているか、PHPのバージョンは composer.json の制約に合っているか、必要な拡張は入っているか、storage/ は書けるか。今まで頭の中のチェックリストだったものがコードになった。

composer require laravel/doctor --dev
php artisan doctor
php artisan doctor --fix

面白いのは、アプリを local と production のどちらかのモードに解決してから判定するところだ。sync のキュー接続はローカルではパスし、本番では警告になる。ブートストラップキャッシュが無いのは本番では警告だが、ローカルでは逆に「ある」ほうが notice になる(古いキャッシュのせいで変更が反映されない、という定番の詰まり方を拾うため)。診断はクラス単位で、パッケージが自前の診断をサービスプロバイダから登録できる。--format=json と --format=github に加え、Laravel Agent Detector がコーディングエージェント上での実行を検知すると、そのまま対処できる指摘だけを1行JSONで出す形式に自動で切り替わる。受託の引き継ぎでは、php artisan doctor --format=json の出力を納品物に添えておくと話が早い。

Laravel Head は <head> の中身、つまりタイトル、meta description、canonical、OGP、robots、JSON-LD、リソースヒントをメソッドチェーンで組み立てる公式パッケージ。メタデータはページ既定、ルートグループ、ルート、実行時、エラーの5層で、上位が下位をフィールド単位で置き換える(実行時のタイトルがルートのタイトルを上書きしても、ルートの description は残る)。

use Laravel\Head\Enums\OgType;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(function (HeadBuilder $head) {
    $head
        ->title('Laravel', suffix: ' - Laravel')
        ->description('Build something great.')
        ->canonical()
        ->og(siteName: 'Laravel', type: OgType::Website)
        ->searchableByRobots()
        ->preconnect('https://fonts.example.com');
});

Blade と Livewire はレイアウトに @head を置くだけ。Inertia では解決済みのタグを head prop として全ページオブジェクトに乗せ、Inertia 3.5+ の serverHead オプションで受ける。初期HTMLにタグが入るので、クローラやリンクプレビューのボットが JavaScript を実行せずに読める。自前のメタタグ管理をコントローラやビューに散らしているアプリなら、これに寄せると置き場所が1つになる。PHP 8.3 と Laravel 13.17 以降が必要。

公式 Laravel Zed 拡張 は、Laracon で発表された Laravel LSP を Zed に繋ぐもの。7月30日に v0.1.0 がレジストリに載った。PHP と Blade で補完、ホバー、診断、ドキュメントリンク、コードアクションが効く。config キー、ルート名、ビューのパス、翻訳文字列、ミドルウェア、コンテナのバインディング、Livewire コンポーネント、Inertia のページを補完して、存在しないものに警告を出す。

注目パッケージ(テーマ別)

AI運用:可観測性とガードレール

  • Laravel AI SDK 0.11:エージェントの1回の実行に単一の相関IDが付き、プロバイダとの往復1回ごと、ツール呼び出し1回ごとに開始と終了のイベントが飛ぶようになった。これまでは5往復かかった実行と1回で終わった実行が外から同じに見え、途中で落ちた実行は何も報告しなかった。prompt() が先頭で invocation ID を発行するので、3プロバイダのフェイルオーバーが1つの実行として繋がる。あわせて、接続失敗(ConnectionException → ProviderConnectionException)がフェイルオーバー対象になり、過負荷とみなすステータスが 503 だけから 502/503/504/520/522/524 に広がり、Anthropic の利用上限エラーも拾うようになった。素の 500 は意図的に外してある(何度投げても同じように失敗するエラーを、フェイルオーバーが覆い隠してしまうため)。

  • エージェント実行のライフサイクルイベント:StartingStep / StepCompleted / StepFailed が往復ごとに、ToolFailed がツール失敗時に飛ぶ。StepCompleted はステップのレスポンス全体と、プロバイダ呼び出しに費やしたミリ秒(float $time)を持つ。単位は QueryExecuted::$time に合わせてあるので、クエリのタイミングを扱っているコードがそのまま流用できる。

use Laravel\Ai\Events\StepCompleted;

Event::listen(StepCompleted::class, function (StepCompleted $event) {
    Log::info('AI step completed', [
        'invocation' => $event->invocationId,
        'step' => $event->stepNumber,
        'ms' => $event->time,
        'prompt_tokens' => $event->response->usage->promptTokens,
        'finish' => $event->response->finishReason->value,
    ]);
});
  • 生のHTTPレスポンス $response->raw:v0.10.3 で入った。SDKが実際に投げた Illuminate\Http\Client\Response をそのまま持つので、レートリミットヘッダやプロバイダ側のリクエストIDが読める。Http::get() の戻り値と同じものなので header() も json() も使える。ストリーミング、Bedrock、シリアライズ済みレスポンス、フェイクでは null になるので ?-> を付ける。
$response->raw->header('x-ratelimit-remaining-requests');
$response->raw->json('id');
  • ToolSearch によるツールの遅延ロード:エージェントが公開するツール定義は、これまで毎リクエスト全部プロバイダに送られていた。4個なら問題ないが30個になると固定コストが重く、モデルの選択肢も増えて選び間違えやすくなる。ToolSearch で包んだツールは、OpenAI と Anthropic のホスト型ツール検索経由で、モデルが使うと決めたときだけ定義が展開される。
use Laravel\Ai\Providers\Tools\ToolSearch;

public function tools(): iterable
{
    return [
        new LookupAccount,
        new ToolSearch(tools: [
            new IssueRefund,
            new ChangePlan,
            new ResendInvoice,
        ]),
    ];
}

ツール側の変更は要らない。Anthropic の検索戦略は第2引数で regex か bm25。対応しないプロバイダはリクエスト送信前に LogicException を投げる(黙ってツールを落とさない)。1リクエストにつきラッパーは1つまで。OpenAI のホスト型検索は保存済みレスポンスを要求するので、store=false の運用では使えない。

  • Laravel Tackle:AIコーディングエージェントを Artisan コマンドとしてアプリの中で走らせるパッケージ。フレームワークと一緒にブートするので、ツールが「自分で使うもの」になる。ルート一覧を引き、Telescope の例外を読み、DBに SELECT を投げ、編集後に Pint を呼ぶ。パス制限、Artisan の許可リスト、セッションごとの上限金額は PHP のコードと設定ファイルにあり、モデルが書き換えられる場所にない。
'artisan_allowlist' => [
    'local'      => ['make:*', 'migrate:*', 'db:seed', 'route:list', 'test'],
    'staging'    => ['migrate', 'route:list'],
    'production' => ['route:list'],
],

ai:code(対話セッション)、ai:run(非対話)、ai:fix(例外やSentry issueから)、ai:review(差分やPRにインラインコメント)、ai:upgrade、失敗したキュージョブを隔離ワークツリーで直してPRを出すセルフヒーラーを持つ。tackle:mcp で Laravel を知っているツール群を MCP 経由で Claude Code や Cursor に出せるのも実用的だ。protected_paths は読み取りも塞ぐので、エージェントが .env を見て引用することはできない。

  • Laravel Auditor:AIエージェントにアプリの監査をさせると、本物のバグとスタイルの好みと存在しない脆弱性が混ざって返ってくる。このパッケージはチェックを自分では実行せず、手元のエージェント(Claude Code、Codex、Cursor、Copilot ほか)に監査の方法論と75個のルールカタログ、そして事実を返す読み取り専用ツールを渡す。すべての指摘は AUD-SEC-001(認可境界の欠落)のような安定IDを引用する。所見は構造化JSONで、auditor:report が Markdown / JSON / CLI / SARIF に整形し、auditor:ci --fail-on=high が終了コードに変える。開発初期段階のパッケージである点は留意。

  • Laravel Boost のプロジェクトルール:.ai/rules に置いた Markdown に paths: フロントマターでグロブを書くと、エージェントはそのパスを触るときだけそのルールを読む。5ページに育った CLAUDE.md が毎セッション全部読まれる問題への答えだ。

---
paths:
  - app/Jobs/**
---

.ai/rules/index.md がグロブとルールの対応表になっていて、エージェントは計画や編集の前にここを見る。ルールはリポジトリに入るので、レビューを通り、チーム全員のエージェントに効く(エージェント自身のメモリは1台の1人に留まる)。既存アプリ向けには infer-conventions スキルがあり、コードが実際にやっていることを根拠付きで拾い上げる。v2.5.0 から既定で有効。

  • Laravel Boost v2.6.0:分散していた pest-testing / enforce-testing / phpunit-guidelines を単一の testing-best-practices スキルに統合し、インストール済みのテストパッケージに応じて動的に構成する。加えて DatabaseQuery MCP ツールが、キーワード検査ではなくデータベース側の読み取り専用トランザクションで書き込みを拒むようになった(MySQL/MariaDB は SET TRANSACTION READ ONLY、Postgres は同等、SQLite は PRAGMA query_only = ON)。データ変更を伴うCTEのように字句解析では見抜けない形があるので、この直し方で正しい。

キューとデータ運用

  • Saga Lara Flow:長時間かかる業務プロセスを、1つの handle() メソッドに上から順に書く。カード決済、在庫引き当て、配送手配を並べるだけで、ジョブチェーンにも状態機械にも分解しない。成立させているのは、各ステップの完了をDBに記録しておく仕組みで、ワーカーが再開すると handle() を再実行するが、$this->action() が完了済みステップを横取りして記録済みの結果を返す。未実行のコードに到達したところから通常の実行に戻る。
public function handle(string $orderId): void
{
    $this->action(ChargeCard::class, $orderId)
        ->compensateWith(RefundCard::class, $orderId)
        ->run();

    $this->action(ReserveStock::class, $orderId)
        ->compensateWith(ReleaseStock::class, $orderId)
        ->run();

    // ここで落ちたら ReleaseStock、次に RefundCard が走る
    $this->action(ShipOrder::class, $orderId)->run();
}

補償(ロールバック)は逆順に実行される。人間の承認や外部コールバック待ちは $this->signal() で、ワーカーを解放したまま中断できる。UUIDやタイムスタンプのような非決定的な値は sideEffect() で包んで初回の結果を再生させる。決済と在庫が絡む処理で、途中で落ちたときに何を戻すかを毎回手で書いているなら、置き換えを検討する価値がある。

  • Laravel Chores:1000万行のバックフィルが4時間走ったところでデプロイに再起動されると、どこまで処理したか分からなくなる。このパッケージはその手の一回限りのデータ操作をバッチに包み、バッチごとに進捗を chore_runs テーブルに記録するので、中断しても続きから再開する。ページングはオフセットではなく主キーによるキーセット方式で、更新した行がチャンクからずれていく定番のバグを避けている。
class NormalizePhoneNumbers extends Chore
{
    public int $batchSize = 500;

    public function collection(): Builder
    {
        return User::whereNotNull('phone')
            ->where('phone', 'not like', '+%');
    }

    public function process($record): void
    {
        $record->update([
            'phone' => PhoneNumber::parse($record->phone, 'EG')->toE164(),
        ]);
    }
}

例外を投げたレコードは失敗テーブルに記録してスキップし、実行は続く。chore:failures で確認し chore:retry で入れ直す。チェックポイントはバッチ単位でレコード単位ではないので、process() は冪等に書いておくのが安全だ。Redisもキューワーカーも要らず、状態はDBだけに置く。

  • Laravel Lock:Cache::lock() は前からあるが、キーの書式もオーナートークンも毎回自分で書き、finally で解放するのを忘れないようにする必要があった。アクション名と対象を取るビルダーに包んだのがこれだ。
$manifest = Lock::for('shipment_dispatch', $shipment)->block(function () use ($shipment, $carrier) {
    return $carrier->dispatch($shipment);
});

HasLocks トレイトでモデル単位のロック(キーはモーフクラスと主キーを含む)、lock ミドルウェアでルート単位のロックが使える。ドライバは cache と database の2つで、後者はキャッシュのフラッシュを跨いで残り、Eloquent で問い合わせられる。ロックが取れなかったときに投げる LockAcquisitionException は RuntimeException を継承していて Laravel のHTTP例外ではないので、そのままだと500になる。コードは 423 が入っているがレスポンスには反映されないため、render() で拾って自分でステータスを決める必要がある点は注意。

  • Laravel Discount:割引の条件をコントローラに直書きせずにEloquentモデルに載せる。パーセンテージと固定額、上限額(max_discount_amount)、クーポンコード、有効期間、総利用回数と顧客ごとの利用回数、最低注文金額、そしてスタック可否まで扱う。
$result = LaravelDiscount::apply($discount, 200);

$result->originalAmount;   // 200.0
$result->discountAmount;   // 40.0
$result->payableAmount();  // 160.0

実装で感心したのは redeem() だ。トランザクションの中で where 句に上限を入れて used_count をインクリメントするので、更新行数が0なら上限に達していたと判定できる。100人目の枠を2人が同時に取ることはない。ゲストはセッションIDで同じ制限がかかる。

テストと品質まわり

  • Double:Laravel Shift の Jason McCreary によるテストダブルのライブラリ。モックとスパイと部分モックを使い分けるのをやめて、種類は1つにして、あとから使う動詞で挙動を決める。Double::for() がクラス、インターフェース、複数インターフェース、実インスタンスを受け取る。モードは loose(未設定の呼び出しに型安全な値を返す。既定)、strict(投げる)、passthru(実オブジェクトに委譲)の3つ。expects() は必ず呼ばれるべき呼び出し、allows() は呼ばれてもよい呼び出し。スパイを宣言していなくても received() で後から確認できるのが実務では効く。PHP 8.3 以上、執筆時点で v0.4.0。

  • Laravel Truss:/truss を開くとER図がブラウザに出る。読むのは構造だけ(テーブル、カラム、キー、インデックス)で行データには一切触らない。テーブルをクリックすると外部キーで繋がる近傍だけを残すフォーカスモード、マイグレーション以降のスキーマ差分、主キーの無いテーブルやインデックスの無い外部キーを検出するスキーマドクターがある。

php artisan truss:diff
php artisan truss:doctor
php artisan truss:export --format=dbml --output=docs/schema.dbml

エクスポートの出力は毎回同じになるので、--check を CI に入れておくと、マイグレーションを変えたのにドキュメントを更新していないビルドを落とせる。Mermaid とフォントを同梱しているのでオフラインでも動き、厳しいCSP下でも壊れない。スキーマ図をレビューに回している現場なら、truss:diff --check を CI に入れるところから試すのが早い。

  • Filament v4.12 / v5.7 の性能改善とセキュリティ修正:フォームとテーブルの描画が大幅に速くなった。TextInput が 0.559ms → 0.044ms(約92%減)、10項目2フィールドの Repeater が 28.077ms → 2.885ms(約90%減)、50行5カラム3アクションのテーブルが 27.53ms → 13.11ms(約52%減)。いずれも開発チームの内部ベンチマークで、アプリによって結果は変わるとの但し書きが付いている。中身は別々で、フォーム側は繰り返し描画される Blade コンポーネントの経路を直接描画に置き換えたもの、テーブル側は軽量な描画経路の追加と属性処理の効率化、そして可視性判定と認可判定のレコード単位キャッシュだ。同時に複数のCVEを解消するセキュリティ更新も入っているので、v4/v5 を使っているなら上げたほうがいい。クエリビルダの maxRules() / maxNestingDepth()、Str::sanitizeCssColor()、CSVの数式インジェクション対策(オプトイン)も追加されている。

  • Statamic Mailables Viewer:アプリ内の Laravel mailable を見つけて、Statamic のコントロールパネルの Utilities → Mailables から確認できる無料アドオン。web.php にコメントアウトして置きっぱなしの /preview-email/{email} ルートを捨てられる。サンプルのコンストラクタデータを自動生成し、スカラー値をその場で編集しながらプレビューと封筒情報の変化を見られる。デスクトップ/タブレット/モバイル表示の切り替えと、キュー経由の mailable も sendNow() でテスト送信できる。

開発環境とエコシステム

  • NativePHP v4(SuperNative):Blade コンポーネントを、WebViewもHTMLも介さずに iOS の SwiftUI ビューと Android の Jetpack Compose ビューとして描くようになった。コンポーネント群は EDGE(Element Definition and Generation Engine)と呼ばれ、独自の Blade エンジンが HTML ではなく固定長のバイト配列に変換し、ネイティブ側のインタプリタがそれを読んでビューを組み立てる。PHPとネイティブ層がメモリを直接共有するので、ブリッジの往復が無い。
<column class="flex-1 p-6 gap-4 bg-theme-background safe-area">
    <text class="text-2xl font-bold text-theme-on-background">
        {{ str($status)->headline() }}
    </text>

    <pressable @tap="confirmReceipt" class="px-6 py-4 rounded bg-theme-primary items-center">
        <text class="text-theme-on-primary font-semibold">Confirm receipt</text>
    </pressable>
</column>

画面は NativeComponent を継承したPHPクラスとBladeビューで、public プロパティが状態、public メソッドがアクション、#[Poll] や #[Locked] などの属性が使える。要するに Livewire の形だ。本物の SwiftUI / Compose ビューなので VoiceOver も TalkBack もダイナミックタイプも既定で効く。テストは Pest で、FakeBridge がコンポーネントの発行したツリーを捕まえるのでシミュレータ無しでCIに載る。既存の v3 アプリは書き直さなくてよく、WebView は <webview php url="/" fullscreen /> という「ネイティブ画面の中に置く1コンポーネント」になった。1画面ずつ移せる。破壊的変更は Device / Dialog / File / System の4プラグインがコアに入ったことだけ。

  • Lerd:Linux と macOS 両対応の、完全に無料でオープンソース(MIT)なローカル開発環境。macOS では Herd が事実上の標準だが、Linux では動かず、かつ日常的に使うダンプビューア、メールキャプチャ、DBサービスは Herd Pro の側にある。

    lerd link 一発でプロジェクトが project.test に立ち上がり、信頼済みHTTPS、プロジェクトごとのPHPバージョン、必要なサービスまで用意される。実体は rootless な Podman コンテナと、Linux では systemd、macOS では launchd のユーザーサービスだ。

    デバッグ側が厚い。dump() と dd() を横取りしてダッシュボードに流し、SQLのN+1と遅いクエリ、送信メール、描画ビュー、イベント、キュージョブをリクエスト単位でまとめる。プロジェクトにパッケージを1つも入れずに、だ。git worktree ごとに独立したサブドメインとDBが割り当たるので、feature ブランチでマイグレーションを走らせても main のデータに触らない。並列でAIエージェントを走らせる使い方も想定されている。

    macOS では Podman VM の上で動くぶん、Herd のネイティブプロセスほど軽くはない。そのトレードオフは公式に明記されている。

  • FrankenPHP で IP アドレスに Let's Encrypt の HTTPS:serversideup/php の Docker イメージ v4.6.0-beta1 が CADDY_ACME_PROFILE を追加した。shortlived を指定すると Let's Encrypt の短命証明書プロファイル(160時間、2日ごとに更新)を要求する。IPアドレス証明書を発行できるのはこのプロファイルだけなので、IPでしか到達できない FrankenPHP コンテナが DNS 無しで本物のHTTPSを喋れる。1週間だけ立てるステージング、VPN内の社内API、顧客向けデモサーバー。どれもホスト名を決める前からIPでは到達できる。今までは自己署名証明書とブラウザの警告か、前段にリバースプロキシを立てるかだった。更新間隔が2日なので、外向き通信を塞ぐファイアウォール変更をやらかしたときの猶予が4日半しかない点は覚えておいたほうがいい。

  • PhpStorm 2026.2:Artisan コマンド、エラーログ、Laravel Cloud のデプロイをエディタ内に置く専用の Laravel ツールウィンドウが入った。Dashboard / Errors / Laravel Cloud の3タブ構成で、Errors ではローカルまたは Sentry のログを辿り、ログをコピペせずにAIで調査や修正ができる。laravel/passport と stancl/tenancy の認識、PHP 8.5 のパイプ演算子対応、コーディングエージェント向けのスキルマネージャも追加。

  • スターターキットが Vite+ に移行:全 Laravel スターターキットが Vite+(7月2日にベータ公開された統合ツールチェーン)でビルドするようになった。vp という単一CLIが Vite、Rolldown、Vitest、tsdown、Oxlint、Oxfmt、Vite Task を束ねる。実務上の変化は、ESLint と Prettier がフロントエンドのセットアップから消えたことだ。Inertia キットにあった lint / lint:check / format / format:check の4スクリプトは vp check(Oxfmt で整形、Oxlint でリント、型チェックまで1パス)に畳まれた。

  • TypePHP:Swoole チームが、PHPのソースをネイティブ機械語にコンパイルするAOTコンパイラをオープンソース化した。バイトコードキャッシュでもVMでもなく、PHPをC++に変換してからネイティブバイナリを吐く。PHPの構文はそのままにコンパイル時の型情報を足し、ホットパスに静的型付けのC++を出す。動的な値、内部関数、リフレクション、オブジェクトのメタデータは PHPX 経由で Zend ランタイムと相互運用する。コンパイラ自体が全部PHPで書かれていて自己ホスティングしている(tpc バイナリはコンパイラ自身のPHPソースを TypePHP でコンパイルして作る)。実務投入の話ではないが、PHPの実行系がどこへ行こうとしているかの1つの答えではある。

今月のトレンド

画像APIが一通りそろった

13.24 で主要色の抽出とHEIC入出力、13.25 で Responsable 化と Image::fromStream() と toFormat() の公開。アップロードを受けて変換して保存する書き込み側は前からできていたが、変換した画像をHTTPで返す読み出し側が3行のボイラープレートだった。ここが埋まったことで、サードパーティのラッパーを入れる理由が減った。iPhone からの HEIC を変換なしで受けられるのも、実際のフォーム実装では効く。

キューを止める、付け替える、見る

queue:pause --all でデプロイ中に全部止め、Queue::forward() でジョブクラスを触らずに流し先を変え、JobReleased と UniqueJobSkipped と JobTimedOut::$timeout で今まで無言だった挙動を拾う。停止中のワーカーが出力を出すようになったのも、ハングと見分けがつかない問題への地味な答えだ。合わせて、大量データ操作を再開可能にする Chores のようなパッケージも出てきている。

AIに境界を引く道具

AI SDK 0.11 の実行IDとステップイベントは、要はAIの呼び出しをAPMのスパンとして扱えるようにする変更だ。ToolSearch はトークンコストの最適化、raw はレートリミットとリクエストIDの取得。一方で Tackle は上限金額とパス制限を設定ファイル側に置き、Boost のプロジェクトルールはエージェントへの指示をリポジトリに入れてレビュー対象にした。Auditor はエージェントの監査結果に嘘が混ざる問題を、ルールIDと読み取り専用の事実収集で潰しにいっている。どれも、AIを本番の作業に入れて最初に詰まるところだ。

深掘り記事

コア各節で挙げた個別記事を、テーマごとにまとめて再掲しておく。実際に実装するときはこちらが早い。

まとめ

今月ぶんで最初に手を付けるなら mask_bindings_in_exception_messages だ。既存アプリでも設定1行で、ログと failed_jobs に個人情報が流れる経路をひとつ塞げる。詳細は各出典リンクから。来月も続ける。

1
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
1
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?