> ## 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 類別的機制，以及以鏈式構成多個處理步驟的 Pipeline 模式。

## 什麼是 Pipeline 模式

Pipeline 模式是將處理對象物件（passable）依序通過一連串 pipe（處理步驟）的設計模式。每個 pipe 接收前一步驟的輸出，加上處理後傳給下一個步驟。

Laravel 中，`Illuminate\Pipeline\Pipeline` 類別實作了此模式。中介層的處理正是以此機制運作。

```
Request → [中介層1] → [中介層2] → [Controller] → Response
```

## Laravel Core 中的使用位置

Pipeline 廣泛用於 Laravel 的核心。

* **HTTP 中介層** — `Illuminate\Foundation\Http\Kernel` 將請求通過中介層的 Pipeline
* **Console 指令** — Artisan 指令的前後處理
* **Router 的中介層** — 路由群組的中介層套用

來看看 HTTP Kernel 使用 Pipeline 的部分。

```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

最單純的 Pipeline 構成。

```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)` — 指定要通過 Pipeline 的物件
* `through($pipes)` — 指定 pipe 的陣列
* `thenReturn()` — 執行 Pipeline 並回傳結果

### then

若希望以 closure 指定最終處理，可使用 `then()`。

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

### pipe

要於事後追加 pipe，使用 `pipe()`。

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

// 依條件插入額外 pipe
if ($needsExtra) {
    $pipeline->pipe(ExtraPipe::class);
}

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

## 類別化的 Pipe

以專用類別取代 closure，可提高可重複利用性。各 pipe 類別實作 `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 — 變更呼叫的方法

預設會呼叫 pipe 的 `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();
```

## 對 Pipe 傳遞參數

於類別名稱使用 `:` 與 `,` 可對 pipe 傳遞參數。這與中介層的 `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 — Pipeline 結束後的處理

使用 `finally()` 方法可註冊 Pipeline 結束後必定執行的 callback。不論成功或失敗都會執行。

```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 以後，可將整個 Pipeline 於資料庫交易內執行。

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

也可以指定特定的資料庫連線。

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

若任一 pipe 發生例外，整個交易會 rollback。

## 實務範例：訂單處理 Pipeline

以下為以多個 pipe 構成電商網站訂單處理的範例。

<Steps>
  <Step title="建立 pipe 類別">
    ```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="執行 Pipeline">
    ```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()` 方法是 Pipeline 的核心部分。以 `array_reduce` 將 pipe 由右向左折疊，建構 closure 的鏈。

```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` 將 pipe 反轉後傳入 `array_reduce`，可正確維持呼叫順序。為使第一個 pipe 首先執行，會從內側堆疊 closure。

<Tip>
  Pipeline 又稱為「洋蔥式」架構。請求由外層（第一個 pipe）向內通過，回應則從內側回到外側。這也是各中介層可於呼叫 `$next()` 前後加上處理的原因。
</Tip>

## 與 Macroable trait 併用

`Pipeline` 類別使用了 `Macroable` trait，因此可以新增自訂方法。

```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 trait" icon="puzzle-piece" href="/zh-TW/advanced/macroable">
  學習為既有類別新增自訂方法的 Macroable trait 用法。
</Card>


## Related topics

- [Macroable trait](/zh-TW/advanced/macroable.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [TCP 模式](/zh-TW/packages/laravel-copilot-sdk/tcp-mode.md)
- [Redis](/zh-TW/redis.md)
- [Client 模式 - 預設集 - VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/client-presets.md)
