> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Pipelineパターン

> Illuminate\Pipeline\Pipeline クラスの仕組みと、複数の処理ステップをチェーンで構成するパイプラインパターンを解説します。

## パイプラインパターンとは

パイプラインパターンは、処理対象のオブジェクト（passable）を一連のパイプ（処理ステップ）に順番に通すデザインパターンです。各パイプは前のステップの出力を受け取り、処理を加えて次のステップに渡します。

Laravelでは `Illuminate\Pipeline\Pipeline` クラスがこのパターンを実装しており、ミドルウェアの処理がまさにこの仕組みで動いています。

```
リクエスト → [ミドルウェア1] → [ミドルウェア2] → [コントローラー] → レスポンス
```

## Laravelコアでの使用箇所

パイプラインはLaravelの中核で広く使われています。

* **HTTPミドルウェア** — `Illuminate\Foundation\Http\Kernel` でリクエストをミドルウェアのパイプラインに通す
* **コンソールコマンド** — Artisanコマンドの前後処理
* **Routerのミドルウェア** — ルートグループのミドルウェア適用

HTTPカーネルがパイプラインを使う部分を見てみましょう。

```php theme={null}
// 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

最もシンプルなパイプラインの構成です。

```php theme={null}
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()` を使います。

```php theme={null}
$result = app(Pipeline::class)
    ->send($request)
    ->through([AuthPipe::class, LogPipe::class])
    ->then(function ($request) {
        return 'processed: ' . $request;
    });
```

### pipe

後からパイプを追加するには `pipe()` を使います。

```php theme={null}
$pipeline = app(Pipeline::class)
    ->send($data)
    ->through([FirstPipe::class]);

// 条件によって追加のパイプを挿入
if ($needsExtra) {
    $pipeline->pipe(ExtraPipe::class);
}

$result = $pipeline->thenReturn();
```

## クラスベースのパイプ

クロージャの代わりに専用クラスを使うことで、再利用性が高まります。各パイプクラスは `handle` メソッドを実装します。

```php theme={null}
namespace App\Pipes;

use Closure;

class TrimPipe
{
    public function handle(string $passable, Closure $next): string
    {
        return $next(trim($passable));
    }
}
```

```php theme={null}
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);
    }
}
```

```php theme={null}
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 === '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
```

## via — 呼び出すメソッドを変更する

デフォルトではパイプの `handle` メソッドが呼ばれますが、`via()` で別のメソッド名を指定できます。

```php theme={null}
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` のような構文と同じ仕組みです。

```php theme={null}
namespace App\Pipes;

use Closure;

class LimitLengthPipe
{
    public function handle(string $passable, Closure $next, int $max = 100): string
    {
        return $next(substr($passable, 0, $max));
    }
}
```

```php theme={null}
$result = app(Pipeline::class)
    ->send($longText)
    ->through(['App\Pipes\LimitLengthPipe:50'])
    ->thenReturn();
```

## finally — パイプライン終了後の処理

`finally()` メソッドを使うと、パイプライン終了後に必ず実行されるコールバックを登録できます。成功・失敗に関わらず実行されます。

```php theme={null}
$result = app(Pipeline::class)
    ->send($request)
    ->through([LogPipe::class, AuthPipe::class])
    ->finally(function ($passable) {
        // ログ記録やリソース解放など
        logger()->info('Pipeline completed', ['request' => $passable]);
    })
    ->thenReturn();
```

## withinTransaction — トランザクション内での実行

Laravel 11以降、パイプライン全体をデータベーストランザクション内で実行できます。

```php theme={null}
$result = app(Pipeline::class)
    ->send($order)
    ->through([
        CreateOrderPipe::class,
        ChargePaymentPipe::class,
        SendConfirmationPipe::class,
    ])
    ->withinTransaction()
    ->thenReturn();
```

特定のデータベース接続を指定することもできます。

```php theme={null}
->withinTransaction('mysql')
```

いずれかのパイプで例外が発生した場合、トランザクション全体がロールバックされます。

## 実践例:注文処理パイプライン

ECサイトの注文処理を複数のパイプで構成する例です。

<Steps>
  <Step title="パイプクラスを作成する">
    ```php theme={null}
    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);
        }
    }
    ```

    ```php theme={null}
    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);
        }
    }
    ```

    ```php theme={null}
    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);
        }
    }
    ```
  </Step>

  <Step title="パイプラインを実行する">
    ```php theme={null}
    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();
        }
    }
    ```
  </Step>
</Steps>

## Pipeline の内部実装

`carry()` メソッドがパイプラインの核心部分です。`array_reduce` を使ってパイプを右から左に畳み込み、クロージャのチェーンを構築します。

```php theme={null}
// 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` に渡すことで、呼び出し順を正しく保ちます。最初のパイプが最初に実行されるよう、クロージャのスタックを内側から積み上げていきます。

<Tip>
  パイプラインは「タマネギ型」アーキテクチャとも呼ばれます。リクエストは外側の層（最初のパイプ）から内側に向かって通過し、レスポンスは内側から外側に戻ってきます。各ミドルウェアが `$next()` を呼ぶ前後に処理を加えられるのはこのためです。
</Tip>

## Macroableトレイトの併用

`Pipeline` クラスは `Macroable` トレイトを使っているため、独自のメソッドを追加できます。

```php theme={null}
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();
```

## 次のステップ

<Card title="Macroableトレイト" icon="puzzle-piece" href="/jp/advanced/macroable">
  既存クラスに独自メソッドを追加するMacroableトレイトの使い方を学びます。
</Card>
