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

Combo Handler Bundle Laravel Package

rednose/combo-handler-bundle

Rednose ComboHandlerBundle integrates combo-handler support into Symfony, letting applications combine multiple CSS or JavaScript assets into a single HTTP response. It reduces request overhead, simplifies asset delivery, and helps improve frontend loading performance.

View on GitHub
Deep Wiki
Context7

Getting Started

You cannot composer require rednose/combo-handler-bundle into a Laravel app and get anything useful. It is a Symfony 2.x bundle — it wires into AppKernel, routing.yml, the Symfony DI container and the legacy HTTP kernel. None of that exists in Laravel. The honest first step is to treat it as a reference implementation and build the combo-handler behavior natively.

  1. Read the source first, install nothing. Clone the repo and read three things: the bundle's routing config (how /combo?files=... is parsed), the loader/resolver service (how it turns a list of paths into files on disk), and the response builder (how Content-Type, Last-Modified, ETag/cache headers are set). Those three concerns are the entire feature. Everything else is Symfony plumbing you will replace.
  2. Confirm you actually need it. In a Laravel app you almost certainly do not: Vite (Laravel's default) bundles at build time, and modern stacks (HTTP/2, CDN) remove the per-request penalty a combo handler was invented to fix. Only proceed if your asset set is genuinely dynamic at request time — per-tenant files, a plugin ecosystem, CMS-injected assets — so a build step cannot enumerate them.
  3. Reimplement as a Laravel route + controller. A single named route (Route::get('/combo', ComboController::class)), a dedicated service that resolves + concatenates, and a cache layer (filesystem or Redis) keyed by the sorted list of files plus a version hash.
  4. First use case: take a Blade partial with three <script src> tags for your own JS files and collapse them into one <script src="{{ route('combo', ['files' => 'js/a.js,js/b.js,js/c.js']) }}">. Verify the single response, the correct Content-Type, and a cache hit on the second request before you touch anything else.

If you are genuinely on an old Symfony 2 codebase (and it happens — legacy projects linger), then the bundle-level steps apply: register the bundle in AppKernel.php, import its routing, and configure the allowed asset roots. But pin the version explicitly in composer.json; with zero stars and zero dependents nobody is maintaining it, and Symfony 2 is end-of-life.

Implementation Patterns

The package is a design reference; here are the patterns worth lifting and how they map to Laravel primitives.

Route + allowlist + resolver (the core loop). The bundle's request shape is a single endpoint with a comma-separated files parameter. In Laravel:

Route::get('/combo/{files}', ComboController::class)
    ->where('files', '[A-Za-z0-9/_.\-,\x20]+');

Resolve each entry against a configured root (config('combo.roots') — public_path('assets'), public_path('vendor')), normalize with realpath(), and reject anything that does not str_starts_with the resolved root. This is your path-traversal defense and it is not optional — a combo endpoint that trusts a client-supplied file list is a file-disclosure vulnerability.

Extension → content-type mapping. The bundle maps extensions to MIME types and refuses mismatched or unknown extensions. Do the same with a fixed array (.css/.js only, realistically). Derive the response Content-Type from the requested extension set, and reject requests that mix extensions (a a.css,b.js combo has no correct content type).

Cache-keyed concatenation. The expensive part is disk I/O and concatenation; cache the merged output keyed by a hash of the normalized file list plus each file's mtime (or a git SHA / asset version). Store it under storage/app/combo/ or Redis. Serve from cache with Cache-Control: public, max-age=..., an ETag from the cache key, and a Last-Modified from the newest mtime. On a deploy, the version component invalidates naturally.

Conditional requests. Handle If-None-Match / If-Modified-Since and return 304. The bundle sets cache-friendly headers; replicating that is a handful of lines and is what makes the endpoint worth having.

Minification hook. The bundle is extensible with custom loaders — the equivalent is a pipeline in your service: concat → (optional) minify → cache. Run minification once, at cache-write time, never per request.

Integration tip — Blade macro. Wrap the URL generation in a helper so views stay clean:

Blade::directive('combo', fn ($expr) => "<?php echo combo_url($expr); ?>");

When the bundle is real. If you are legitimately on Symfony 2, the patterns are the same: define allowed source roots in bundle config, import the bundle routing under a /combo prefix, and extend the resolver service (register it in services.yml under the bundle's service id) for custom loaders. The extension point is the loader/resolver service; swap it rather than editing vendor code.

Gotchas and Tips

  • Symfony 2 is EOL. This is the headline. No security patches, no PHP 8 compatibility guarantees, no community. If you are porting, you own all of it. Do not put this package on a critical path in a Laravel app, and do not stand up a sidecar Symfony 2 runtime to run it — the operational cost dwarfs the benefit.
  • Zero stars, zero dependents. That is not a quality signal in itself, but it does mean no one is triaging issues, no CVE response, and no second pair of eyes on path handling. Budget for reading every line you rely on.
  • Path traversal is the primary risk. A combo handler takes a client-supplied file list. .. sequences, absolute paths, symlink escapes, URL-encoded variants, and null bytes are all live attack vectors. Allowlist roots, realpath(), reject symlinks, and validate extension before touching the filesystem. Write tests for each of these — an untested combo endpoint is a bug waiting to be found by someone else.
  • Cache poisoning. If the cache key is the raw query string, a.css,b.css and b.css,a.css produce two entries for identical output — cardinality blows up, and a crafted list can fill your cache. Normalize (sort, dedupe, canonicalize paths) before hashing.
  • Cache stampede. Under load, N concurrent requests for an uncached combo will all concatenate. Use an atomic write (write to temp, rename()) and consider a lock or a short "stale-while-revalidate" window. Partial writes served to clients are the ugly failure mode here.
  • Stale assets after deploy. The classic combo-handler bug: file changed, cache key did not. Include mtime or a build version in the key, and invalidate at deploy time. If you front this with a CDN, remember the CDN holds the stale copy — purge on release.
  • Content-type confusion. Mixing .css and .js in one request, or sniffing extensions loosely, leads to browsers interpreting a stylesheet as script. Enforce single-extension per request and set the header explicitly. Add X-Content-Type-Options: nosniff.
  • Compression side channels. If you gzip combo output containing secrets (tokens embedded in JS), you widen a BREACH-style attack surface. Usually fine for static assets, but know it is there.
  • Debugging: the bundle resolves files at request time, so "works in dev, 404 in prod" almost always means a path-root or permission difference. Log the resolved absolute path on miss, not just the requested token.
  • Extension point if you fork: the resolver/loader is where you customize (e.g., pulling from S3, or per-tenant roots). Keep it behind an interface you own so a future replacement (Vite, CDN concat, nginx concat module) is a swap rather than a rewrite.
  • The bigger picture for a Laravel dev: every hour spent hardening a runtime combo endpoint is an hour not spent on Vite + HTTP/2, which solves the same problem with maintained tooling. Choose the combo handler only when the asset set is genuinely dynamic, and when you do, own it as first-party code under test — not as a dependency.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib