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

Multiplex Bundle Laravel Package

liip/multiplex-bundle

LiipMultiplexBundle lets a Symfony project serve multiple applications or websites from one codebase, routing requests by host or configuration so each app can keep separate settings and templates while sharing common logic.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing: you cannot install this. liip/multiplex-bundle is a Symfony2 bundle and the repository is archived. composer require liip/multiplex-bundle will fail against any modern illuminate/* component because the bundle pins symfony/symfony to the 2.x range. Forcing it with --ignore-platform-reqs gets you a bundle that Laravel's kernel will never boot. So "getting started" here means reading it as a reference implementation and building the two useful ideas natively. Budget 1–2 days.

Where to look first (in order):

  1. The bundle's DependencyInjection/ extension and Resources/config/ — this shows how kernels were registered and selected. Skim only.
  2. The kernel.request event listener — this is the actual multiplexer entry point. It parses the request payload into sub-request descriptors and dispatches them.
  3. The controller that iterates the descriptors and calls HttpKernelInterface::handle() — this is the ~100 lines worth copying conceptually.
  4. The JSON-coercion listener — the "any controller can emit JSON" idea. Laravel already solves this with JsonResource / Responsable, so read it for the envelope shape and move on.

Minimal viable Laravel equivalent — your first use case. A dashboard that renders 8–10 widgets, each backed by its own endpoint, currently firing 10 requests per screen load. Replace with one batched call. Start with a route and a FormRequest that validates an array of descriptors:

// routes/api.php
Route::post('/v1/batch', BatchController::class)->middleware('auth:sanctum');

// BatchRequest
public function rules():array {
    return [
        'operations'          => ['required','array','max:10'],
        'operations.*.id'     => ['required','string','distinct'],
        'operations.*.method' => ['required','in:GET,POST'],
        'operations.*.path'   => ['required','string','starts_with:/api/'],
        'operations.*.body'   => ['array'],
    ];
}

First use case to ship: internal-only aggregation. Do not start with external fan-out — internal sub-requests against your own routes are cheap and safe; external fan-out drags timeouts, retries, and circuit breakers into a single request and is a much bigger design exercise. Prove the round-trip win first.

Implementation Patterns

Pattern 1 — external fan-out with Http::pool(). The common case: the aggregator calls other services or your own HTTP surface concurrently.

$responses = Http::pool(function (Pool $pool) use ($ops, $baseUrl) {
    return collect($ops)->map(fn ($op) => $pool
        ->timeout(2)
        ->withHeaders(['X-Request-Id' => (string) Str::uuid()])
        ->{strtolower($op['method'])}($baseUrl.$op['path'], $op['body'] ?? [])
        ->as($op['id'])
    )->all();
});

Http::pool() returns per-request responses including connection failures, which is exactly what you need for partial-failure semantics. Do not let a single failed sub-request throw and discard the rest.

Pattern 2 — the response envelope. This is the part the old bundle got right and the part you must copy. Every sub-result carries its own status:

return response()->json([
    'results' => collect($ops)->map(fn ($op) => [
        'id'     => $op['id'],
        'status' => $responses[$op['id']]->status(),
        'ok'     => $responses[$op['id']]->successful(),
        'body'   => $responses[$op['id']]->successful()
                        ? $responses[$op['id']]->json()
                        : ['error' => $responses[$op['id']]->reason()],
    ])->values(),
], 207); // 207 Multi-Status — a 200 with silent nulls is an incident waiting to happen

The top-level HTTP status should be 207 for partial success, 200 for all-success, 502/500 only when everything failed. Never return 200 with null bodies.

Pattern 3 — internal dispatch when you must hit your own controllers. Laravel was not designed for re-entrant kernel handling; nested app(Kernel::class)->handle() calls multiply middleware execution and memory per inbound request. Prefer restructuring the target actions into service classes and calling them directly. If you truly need sub-requests:

$request = Request::create($op['path'], $op['method'], $op['body'] ?? []);
$request->headers->set('Authorization', $original->header('Authorization')); // propagate auth explicitly
$response = app(\Illuminate\Contracts\Http\Kernel::class)->handle($request);

Guard this behind a hard cap (5–10 sub-requests) and a wall-clock budget.

Pattern 4 — uniform JSON-ification. The bundle's second idea. Laravel-native equivalent is a Responsable or a JsonResource wrapper, plus content negotiation via Accept header middleware. Do not build a listener that sniffs return types; JsonResource already does this cleanly and is testable.

Pattern 5 — allowlist, not arbitrary URIs. The client must never specify a raw URL. Accept an operation name and resolve it server-side:

// config/batch.php
return ['operations' => [
    'orders.recent'  => ['method' => 'GET', 'path' => '/api/orders?limit=5'],
    'profile.summary'=> ['method' => 'GET', 'path' => '/api/me/summary'],
]];

This kills SSRF, closes the endpoint to third-party abuse, and keeps the contract stable when internal paths change.

Integration tips:

  • Auth propagation is the trickiest part. A batch endpoint receives one bearer token and must decide how it applies to each downstream call. Propagate the original token to internal routes; use a service token for external calls, but only after an authorization check that the caller is entitled to the aggregated resource.
  • Caching breaks down under aggregation — a per-widget cache TTL does not translate cleanly to a composite response. If you need it, cache sub-results individually inside the aggregator and assemble from cache.
  • Feature-flag the endpoint and instrument fan-out depth plus aggregate p50/p95 latency before exposing it to clients.
  • Map the response to id, never array position. Clients reorder the operations array; matching by index is a subtle class of bug.

Gotchas and Tips

Composer and platform traps.

  • composer require fails on constraint resolution against Symfony 6/7. Do not "fix" this by widening constraints or vendoring the bundle — you will be maintaining a permanent internal fork of dead code.
  • No PHP 8 typed-property or strict-types discipline; the code will emit deprecations on 8.1+. There is no PHP 8.4 compatibility work upstream and there never will be.

Security and maintenance — the reason this is a reference only.

  • Archived, deprecated, 12 stars, zero dependents. No advisories, no issue triage, no Stack Overflow corpus.
  • The bundle era spans Symfony 2.x, which hit EOL in November 2018. Any transitive code inherits a long tail of unpatched CVEs (HTTP Foundation, Serializer, YAML, polyfills).
  • Multiplexing is itself an attack-surface amplifier: one request triggers N downstream calls. Without an allowlist, hard cap, and per-item timeout, you have built a DoS primitive against your own infrastructure.

Failure-mode design — decide these before writing a line.

  • Partial failure: 3 of 5 operations succeed. The envelope carries per-item status; the client renders the degraded widget with a retry affordance, not a blank page. This is a product-visible reliability decision, so get sign-off.
  • Head-of-line blocking: one slow downstream sets the latency of the entire batch. ->timeout(2) per request and a bounded aggregate deadline (e.g. Http::pool overall cap) are mandatory, not optional.
  • Cascading retries: a client retrying a failed batch re-runs the whole fan-out, including the calls that already succeeded. If the operations are not idempotent, this is a data-integrity problem. Make GET the default and require explicit opt-in for mutating operations, or drop POST from the contract entirely until you have idempotency keys.

Scaling — multiplexing shifts load, it does not remove it. A batch endpoint pins N downstream calls to a single PHP worker. A handful of concurrent batch clients can saturate both your PHP-FPM pool and your downstream services simultaneously. Set Http::pool concurrency limits (e.g. ->concurrency(5) on the pool) and consider queuing genuinely slow fan-outs rather than holding the worker.

Testing. Http::fake() gives you deterministic coverage of per-item success, failure, and timeout without touching the network:

Http::fake([
    '*/api/orders*' => Http::response(['orders' => []], 200),
    '*/api/me/*'    => Http::response(null, 503),
]);

Assert the 207, the per-item status, and that the failed item carries a machine-readable error rather than a null.

Extension points worth borrowing from the original: the descriptor schema (id, method, path, body) and the per-item response envelope. Both are small, well-shaped, and version them explicitly (/api/v1/batch) so you can evolve them without breaking clients. Everything else — kernel selection, DI wiring, JSON coercion — is either obsolete or already first-class in Laravel; do not reimplement it.

When to reach for something else instead. If the set of screen compositions changes frequently, a hardcoded multiplexer route bakes each composition into server code and becomes a bottleneck. GraphQL (Lighthouse) or letting the client issue parallel calls over HTTP/2 solves the round-trip problem without server-side orchestration. If you only need to standardize JSON responses, that has nothing to do with multiplexing — use JsonResource and stop.

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
cgv/localized-resource