Documentation Index
Fetch the complete documentation index at: https://kawax.biz/llms.txt
Use this file to discover all available pages before exploring further.
パイプラインパターンとは
パイプラインパターンは、処理対象のオブジェクト(passable)を一連のパイプ(処理ステップ)に順番に通すデザインパターンです。各パイプは前のステップの出力を受け取り、処理を加えて次のステップに渡します。
Laravelでは Illuminate\Pipeline\Pipeline クラスがこのパターンを実装しており、ミドルウェアの処理がまさにこの仕組みで動いています。
リクエスト → [ミドルウェア1] → [ミドルウェア2] → [コントローラー] → レスポンス
Laravelコアでの使用箇所
パイプラインはLaravelの中核で広く使われています。
- HTTPミドルウェア —
Illuminate\Foundation\Http\Kernel でリクエストをミドルウェアのパイプラインに通す
- コンソールコマンド — Artisanコマンドの前後処理
- Routerのミドルウェア — ルートグループのミドルウェア適用
HTTPカーネルがパイプラインを使う部分を見てみましょう。
// Illuminate\Foundation\Http\Kernel より
protected function sendRequestThroughRouter($request)
{
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
基本的な使い方
send / through / thenReturn
最もシンプルなパイプラインの構成です。
use Illuminate\Pipeline\Pipeline;
$result = app(Pipeline::class)
->send('hello')
->through([
function (string $passable, Closure $next): string {
return $next(strtoupper($passable));
},
function (string $passable, Closure $next): string {
return $next($passable . '!');
},
])
->thenReturn();
// $result === 'HELLO!'
send($passable) — パイプラインに通すオブジェクトを指定する
through($pipes) — パイプの配列を指定する
thenReturn() — パイプラインを実行して結果を返す
then
最終的な処理をクロージャで指定したい場合は then() を使います。
$result = app(Pipeline::class)
->send($request)
->through([AuthPipe::class, LogPipe::class])
->then(function ($request) {
return 'processed: ' . $request;
});
pipe
後からパイプを追加するには pipe() を使います。
$pipeline = app(Pipeline::class)
->send($data)
->through([FirstPipe::class]);
// 条件によって追加のパイプを挿入
if ($needsExtra) {
$pipeline->pipe(ExtraPipe::class);
}
$result = $pipeline->thenReturn();
クラスベースのパイプ
クロージャの代わりに専用クラスを使うことで、再利用性が高まります。各パイプクラスは handle メソッドを実装します。
namespace App\Pipes;
use Closure;
class TrimPipe
{
public function handle(string $passable, Closure $next): string
{
return $next(trim($passable));
}
}
namespace App\Pipes;
use Closure;
class SanitizePipe
{
public function handle(string $passable, Closure $next): string
{
$sanitized = htmlspecialchars($passable, ENT_QUOTES, 'UTF-8');
return $next($sanitized);
}
}
use App\Pipes\TrimPipe;
use App\Pipes\SanitizePipe;
use Illuminate\Pipeline\Pipeline;
$result = app(Pipeline::class)
->send(' <script>alert("xss")</script> ')
->through([TrimPipe::class, SanitizePipe::class])
->thenReturn();
// $result === '<script>alert("xss")</script>'
via — 呼び出すメソッドを変更する
デフォルトではパイプの handle メソッドが呼ばれますが、via() で別のメソッド名を指定できます。
class ValidatePipe
{
public function process(array $data, Closure $next): array
{
// バリデーション処理
return $next($data);
}
}
$result = app(Pipeline::class)
->send($data)
->through([ValidatePipe::class])
->via('process')
->thenReturn();
パイプへのパラメーター渡し
クラス名に : と , を使ってパイプにパラメーターを渡せます。これはミドルウェアの throttle:60,1 のような構文と同じ仕組みです。
namespace App\Pipes;
use Closure;
class LimitLengthPipe
{
public function handle(string $passable, Closure $next, int $max = 100): string
{
return $next(substr($passable, 0, $max));
}
}
$result = app(Pipeline::class)
->send($longText)
->through(['App\Pipes\LimitLengthPipe:50'])
->thenReturn();
finally — パイプライン終了後の処理
finally() メソッドを使うと、パイプライン終了後に必ず実行されるコールバックを登録できます。成功・失敗に関わらず実行されます。
$result = app(Pipeline::class)
->send($request)
->through([LogPipe::class, AuthPipe::class])
->finally(function ($passable) {
// ログ記録やリソース解放など
logger()->info('Pipeline completed', ['request' => $passable]);
})
->thenReturn();
withinTransaction — トランザクション内での実行
Laravel 11以降、パイプライン全体をデータベーストランザクション内で実行できます。
$result = app(Pipeline::class)
->send($order)
->through([
CreateOrderPipe::class,
ChargePaymentPipe::class,
SendConfirmationPipe::class,
])
->withinTransaction()
->thenReturn();
特定のデータベース接続を指定することもできます。
->withinTransaction('mysql')
いずれかのパイプで例外が発生した場合、トランザクション全体がロールバックされます。
実践例:注文処理パイプライン
ECサイトの注文処理を複数のパイプで構成する例です。
パイプクラスを作成する
namespace App\Pipes\Order;
use App\Models\Order;
use Closure;
class ValidateInventoryPipe
{
public function handle(Order $order, Closure $next): Order
{
foreach ($order->items as $item) {
if ($item->product->stock < $item->quantity) {
throw new \RuntimeException("在庫不足: {$item->product->name}");
}
}
return $next($order);
}
}
namespace App\Pipes\Order;
use App\Models\Order;
use Closure;
class ReserveInventoryPipe
{
public function handle(Order $order, Closure $next): Order
{
foreach ($order->items as $item) {
$item->product->decrement('stock', $item->quantity);
}
return $next($order);
}
}
namespace App\Pipes\Order;
use App\Models\Order;
use App\Services\PaymentService;
use Closure;
class ProcessPaymentPipe
{
public function __construct(
protected PaymentService $payment,
) {}
public function handle(Order $order, Closure $next): Order
{
$this->payment->charge($order->total, $order->payment_token);
$order->update(['status' => 'paid']);
return $next($order);
}
}
パイプラインを実行する
use App\Models\Order;
use App\Pipes\Order\ValidateInventoryPipe;
use App\Pipes\Order\ReserveInventoryPipe;
use App\Pipes\Order\ProcessPaymentPipe;
use Illuminate\Pipeline\Pipeline;
class OrderService
{
public function process(Order $order): Order
{
return app(Pipeline::class)
->send($order)
->through([
ValidateInventoryPipe::class,
ReserveInventoryPipe::class,
ProcessPaymentPipe::class,
])
->withinTransaction()
->thenReturn();
}
}
Pipeline の内部実装
carry() メソッドがパイプラインの核心部分です。array_reduce を使ってパイプを右から左に畳み込み、クロージャのチェーンを構築します。
// Pipeline::then() の内部
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
return $pipe->{$this->method}(...$parameters);
};
};
}
array_reverse でパイプを逆順にして array_reduce に渡すことで、呼び出し順を正しく保ちます。最初のパイプが最初に実行されるよう、クロージャのスタックを内側から積み上げていきます。
パイプラインは「タマネギ型」アーキテクチャとも呼ばれます。リクエストは外側の層(最初のパイプ)から内側に向かって通過し、レスポンスは内側から外側に戻ってきます。各ミドルウェアが $next() を呼ぶ前後に処理を加えられるのはこのためです。
Macroableトレイトの併用
Pipeline クラスは Macroable トレイトを使っているため、独自のメソッドを追加できます。
use Illuminate\Pipeline\Pipeline;
Pipeline::macro('sendThroughLogging', function (array $pipes) {
/** @var Pipeline $this */
return $this->through(array_map(function ($pipe) {
return function ($passable, $next) use ($pipe) {
logger()->debug("Entering pipe: {$pipe}");
$result = $next($passable);
logger()->debug("Exiting pipe: {$pipe}");
return $result;
};
}, $pipes));
});
$result = app(Pipeline::class)
->send($data)
->sendThroughLogging([TrimPipe::class, SanitizePipe::class])
->thenReturn();
次のステップ
Macroableトレイト
既存クラスに独自メソッドを追加するMacroableトレイトの使い方を学びます。