Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pipeline Laravel Package

chefhasteeth/pipeline

A Laravel package for building and running pipelines of reusable stages. Pass a payload through each step, compose stages into classes, and use conditions and dependency injection to keep complex workflows clean and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps Laravel’s pipe-and-filter pattern into a friendlier, container-aware Pipeline class. Install it via Composer and start by looking at the package’s Pipeline class and Pipe contract — those are the two things you’ll interact with most:

composer require chefhasteeth/pipeline

The first, simplest use case is passing a payload through a list of pipe classes:

use Chefhasteeth\Pipeline\Pipeline;

$result = Pipeline::make()
    ->send($payload)
    ->through([
        \App\Pipes\ValidatePayload::class,
        \App\Pipes\EnrichPayload::class,
    ])
    ->then(fn ($payload) => $payload);

Each pipe is a normal class resolved from the container with a handle($payload, Closure $next) method:

namespace App\Pipes;

use Closure;

class ValidatePayload
{
    public function handle($payload, Closure $next)
    {
        // throw, mutate, or short-circuit
        return $next($payload);
    }
}

If you’ve used Illuminate\Pipeline\Pipeline before, this should feel familiar — the main difference is the static make() entry point and cleaner ergonomics out of the box.

Implementation Patterns

Feature-style composition. Define one small class per step (App\Pipes\…) and compose them in a controller, job, or action class. This keeps handle chains shallow and makes each step independently testable.

$order = Pipeline::make()
    ->send($order)
    ->through([
        EnsureStockAvailable::class,
        CalculateTaxes::class,
        ChargeCustomer::class,
        SendReceipt::class,
    ])
    ->then(fn (Order $order) => $order);

Short-circuiting. Pipes can return early instead of calling $next when a precondition fails — a clean way to branch without deeply nested if statements. Throw domain exceptions in place of calling $next to fail fast.

Reusable pipelines as first-class objects. Wrap a fixed list of pipes in a dedicated class or service (OrderPipeline) so tests and controllers don’t repeat the array. This is also the easiest extension point if you later need to swap steps based on config or feature flags.

Dependency injection in pipes. Because pipes are resolved through the container, you can inject services (repositories, HTTP clients, loggers) into the constructor and use them inside handle. Keep the payload immutable where possible, but do pass state forward through $next($payload) — Laravel pipelines usually mutate one canonical object rather than returning new ones.

Integration with form requests and jobs. A common pattern is to build the pipeline inside a form request’s passedValidation() or inside a queued job’s handle(). Both give you a clean payload and clear boundaries around the pipe chain.

Testing. Assert each pipe in isolation by passing a fn ($p) => $p terminal closure. For the whole pipeline, assert on the final payload and mock only the boundaries (HTTP, DB writes).

$result = (new ValidatePayload)->handle($payload, fn ($p) => $p);

Gotchas and Tips

  • Return $next($payload), not $next. Every pipe must call the closure with the payload — forgetting the argument is the most common bug and silently drops your data.
  • Pipes are resolved fresh from the container each run. Don’t store per-request state on a pipe instance as a shared cache — the container may hand you the same instance depending on how it’s bound, so prefer explicit payload passing.
  • then() is required. There’s no implicit execution; if you forget the terminal callback, nothing runs. Use thenReturn() if available, or then(fn ($p) => $p).
  • Exceptions propagate up the pipe stack. A pipe throwing without a try block in a prior pipe will bubble straight out of then(). Wrap the whole pipeline at the call site if you need partial-failure handling, or catch inside a dedicated “wrap” pipe at the start.
  • Debugging a chain. Add a temporary logger()->info() inside each handle or use ray() if installed — pipeline steps are otherwise opaque. Because they’re container-resolved, constructor errors will show up as resolution failures rather than pipeline errors.
  • Config/binding quirks. Bindings that rely on app()->bind() can cause pipes to receive a different implementation in tests than in production. Prefer interfaces bound in a service provider and swap the binding in tests.
  • Extension point: customize the contract. If your team prefers a different method name (handle vs __invoke), check the package’s Pipe contract — subclassing or adapting it is the cleanest way to swap conventions without patching pipes individually.
  • Laravel 12 compatibility. As of v1.0.5, the package is explicitly tested against Laravel 12.x. If you’re on Laravel 10/11 you can stay on v1.0.4 with no behavioral difference; the bump is purely a compatibility constraint change, so no migration work is needed.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager