Zum Hauptinhalt springen

Was ist das Pipeline-Muster?

Das Pipeline-Muster ist ein Entwurfsmuster, bei dem ein Verarbeitungsobjekt (das „Passable”) durch eine Reihe von Pipes (Verarbeitungsschritte) geschickt wird. Jede Pipe erhält die Ausgabe der vorherigen Stufe, wendet ihre Verarbeitung an und reicht das Ergebnis an die nächste weiter. In Laravel implementiert die Klasse Illuminate\Pipeline\Pipeline dieses Muster — die Middleware-Verarbeitung funktioniert exakt nach diesem Prinzip.
Request → [Middleware 1] → [Middleware 2] → [Controller] → Response

Einsatzstellen im Laravel-Kern

Die Pipeline wird im Kern von Laravel breit eingesetzt.
  • HTTP-MiddlewareIlluminate\Foundation\Http\Kernel schickt Requests durch die Middleware-Pipeline
  • Console-Commands — Vor- und Nachverarbeitung von Artisan-Befehlen
  • Router-Middleware — Middleware-Anwendung auf Route-Gruppen
Werfen wir einen Blick darauf, wie der HTTP-Kernel die Pipeline nutzt.
// Aus Illuminate\Foundation\Http\Kernel
protected function sendRequestThroughRouter($request)
{
    return (new Pipeline($this->app))
        ->send($request)
        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
        ->then($this->dispatchToRouter());
}

Grundlegende Verwendung

send / through / thenReturn

Die einfachste Pipeline-Konstruktion:
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) — Das Objekt, das durch die Pipeline geschickt wird.
  • through($pipes) — Das Array der Pipes.
  • thenReturn() — Führt die Pipeline aus und gibt das Ergebnis zurück.

then

Wollen Sie die Endverarbeitung als Closure angeben, verwenden Sie then().
$result = app(Pipeline::class)
    ->send($request)
    ->through([AuthPipe::class, LogPipe::class])
    ->then(function ($request) {
        return 'processed: ' . $request;
    });

pipe

Um nachträglich eine Pipe hinzuzufügen, nutzen Sie pipe().
$pipeline = app(Pipeline::class)
    ->send($data)
    ->through([FirstPipe::class]);

// Bei Bedarf eine weitere Pipe einfügen
if ($needsExtra) {
    $pipeline->pipe(ExtraPipe::class);
}

$result = $pipeline->thenReturn();

Klassenbasierte Pipes

Statt Closures verwenden Sie dedizierte Klassen für bessere Wiederverwendbarkeit. Jede Pipe-Klasse implementiert die Methode 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 === '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'

via — aufzurufende Methode ändern

Standardmäßig wird die Methode handle der Pipe aufgerufen. Über via() können Sie einen anderen Methodennamen setzen.
class ValidatePipe
{
    public function process(array $data, Closure $next): array
    {
        // Validierung
        return $next($data);
    }
}

$result = app(Pipeline::class)
    ->send($data)
    ->through([ValidatePipe::class])
    ->via('process')
    ->thenReturn();

Parameter an Pipes übergeben

Sie können mit : und , am Klassennamen Parameter an Pipes übergeben — dieselbe Mechanik wie die Middleware-Syntax 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 — Verarbeitung nach dem Ende der Pipeline

Mit finally() registrieren Sie einen Callback, der nach dem Pipeline-Ende garantiert ausgeführt wird — unabhängig von Erfolg oder Fehler.
$result = app(Pipeline::class)
    ->send($request)
    ->through([LogPipe::class, AuthPipe::class])
    ->finally(function ($passable) {
        // Logging oder Ressourcenfreigabe
        logger()->info('Pipeline completed', ['request' => $passable]);
    })
    ->thenReturn();

withinTransaction — Ausführung in einer Transaktion

Ab Laravel 11 können Sie die gesamte Pipeline innerhalb einer Datenbanktransaktion laufen lassen.
$result = app(Pipeline::class)
    ->send($order)
    ->through([
        CreateOrderPipe::class,
        ChargePaymentPipe::class,
        SendConfirmationPipe::class,
    ])
    ->withinTransaction()
    ->thenReturn();
Sie können auch eine bestimmte Datenbankverbindung angeben.
->withinTransaction('mysql')
Wirft eine der Pipes eine Exception, wird die gesamte Transaktion zurückgerollt.

Praxisbeispiel: Pipeline für die Bestellverarbeitung

Beispiel für die Bestellverarbeitung eines E-Commerce-Systems, aufgebaut aus mehreren Pipes.
1

Pipe-Klassen erstellen

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("Bestand unzureichend: {$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);
    }
}
2

Pipeline ausführen

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();
    }
}

Interner Aufbau von Pipeline

Die Methode carry() ist das Herzstück der Pipeline. Mit array_reduce werden die Pipes von rechts nach links gefaltet und zu einer verketteten Closure zusammengebaut.
// Intern in 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);
        };
    };
}
Durch array_reverse werden die Pipes umgekehrt und an array_reduce übergeben, damit die Aufrufreihenfolge stimmt. Der Closure-Stack wird von innen nach außen aufgebaut, sodass die erste Pipe zuerst ausgeführt wird.
Pipelines werden manchmal auch „Zwiebel-Architektur” genannt. Der Request durchdringt die Schichten von außen (erste Pipe) nach innen; die Response läuft von innen nach außen zurück. Deshalb kann jede Middleware sowohl vor als auch nach $next() Verarbeitung einfügen.

Kombination mit dem Macroable-Trait

Die Pipeline-Klasse nutzt den Trait Macroable — Sie können eigene Methoden ergänzen.
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();

Nächste Schritte

Macroable-Trait

Lernen Sie, wie Sie mit dem Macroable-Trait bestehenden Klassen eigene Methoden hinzufügen.
Zuletzt geändert am 13. Juli 2026