> ## 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 ipeline ipeline 클래스의 구조와, 여러 처리 단계를 체인으로 구성하는 파이프라인 패턴을 해설합니다.

## 파이프라인 패턴이란

파이프라인 패턴은 처리 대상 오브젝트(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="/ko/advanced/macroable">
  기존 클래스에 고유 메서드를 추가하는 Macroable 트레이트의 사용법을 배웁니다.
</Card>


## Related topics

- [Macroable 트레이트](/ko/advanced/macroable.md)
- [Laravel FullFeed](/ko/packages/laravel-fullfeed.md)
- [PHP Reflection API](/ko/advanced/php-reflection.md)
- [Conditionable 트레이트](/ko/advanced/conditionable.md)
- [PHP FFI](/ko/advanced/ffi.md)
