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

Content Negotiation Bundle Laravel Package

libero/content-negotiation-bundle

Content negotiation bundle for PHP applications. Inspects Accept headers to select the right response format and serializer, so a single endpoint can serve JSON, XML, or HTML. Pluggable negotiators and clean middleware integration for Libero-based Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This release (v0.1.0) makes the package usable for the two most common negotiation setups in a Laravel app: deciding a negotiated value from the route definition (#2) and from a URL path segment (#3). Previously you were effectively limited to whatever ambient signal the package read by default (typically a header), which meant most real apps had to hand-roll a wrapper anyway.

Minimal steps:

  1. Require the package and publish its config: php artisan vendor:publish --tag=<package>-config. The config file is the first thing to read — the resolver/strategy list and its ordering is the whole mental model of this package.
  2. Register the negotiation middleware. Put it early in the web (and api) group — before anything that reads the negotiated value, notably SetLocale, session start, and any URL-defaults middleware.
  3. Confirm the middleware alias and the config keys against the package README rather than guessing; at v0.1.0 the names are not yet stable and the release notes only describe behaviour, not API surface.
  4. First use case to try: a path-prefixed route group (/fr/..., /v2/...) where the middleware reads the segment, validates it against a supported-value list, and exposes the result to the rest of the request lifecycle. Then layer route-level declarations on a single endpoint to see how the two interact.

Implementation Patterns

Path-based negotiation (#3) — the natural fit for locale prefixes and API versioning:

Route::prefix('{locale}')
    ->whereIn('locale', config('app.supported_locales'))
    ->middleware('negotiate')
    ->group(function () {
        Route::get('/pricing', PricingController::class);
    });

Constrain the parameter with whereIn so unsupported values 404 at the routing layer instead of silently falling through to a default. Inside the group, feed the resolved value into URL::defaults(['locale' => $locale]) so route() keeps emitting the prefix without threading it through every call.

Route-level negotiation (#2) — declare acceptable values on the route itself, which is the cleanest option when negotiation is per-endpoint rather than global (different API versions exposing different representations, an endpoint that only speaks one format):

Route::get('/export', ExportController::class)
    ->negotiates(['format' => ['csv', 'json']]);

Treat this as the highest-priority signal: an explicit per-route declaration should win over an implicit path segment or header, because it is the most local and most intentional piece of configuration.

Keep precedence deterministic. With three sources now in play (route declaration, path segment, header/default), write down the order and assert it in tests. A sane default is route > path > header > config default, but the important thing is that it's fixed and documented in your own codebase, not inferred.

Publish the resolved value once. Have the middleware stash the result on the request (attributes) or in a small value object bound to the container, and read it from there in controllers, FormRequests, and Blade/Inertia shared props. Avoid re-parsing the path in multiple places.

Testing. Feature-test the paths and routes directly rather than unit-testing resolvers in isolation — the interesting bugs live in the interaction between route parameters and middleware ordering:

$this->get('/fr/pricing')->assertOk();
$this->assertSame('fr', app()->getLocale());

$this->get('/xx/pricing')->assertNotFound();

Gotchas and Tips

  • Middleware ordering is the number one failure mode. If SetLocale or a session/cookie middleware runs first, it will overwrite the negotiated value and you'll spend an hour debugging a resolver that was working fine. Register negotiation explicitly rather than relying on the order of a service provider's pushMiddleware.
  • Route parameter collisions. A {locale} (or {version}) segment sitting in a global prefix constrains every route in the group, including existing ones. Audit php artisan route:list after enabling it and make sure no existing route already binds a parameter of that name.
  • Long-lived processes leak state. In Octane, queue workers, and Pest/PHPUnit, URL::defaults() and any locale set during a request persist. Reset them in a teardown or in the middleware's terminate/cleanup path — otherwise test order affects results and Octane requests bleed one user's locale into the next.
  • Signed URLs and absolute redirects lose the prefix. Anything generating URLs outside the request's route context (URL::signedRoute, some auth redirects, mailables) needs the negotiated value passed explicitly or URL::defaults set in that context too.
  • Cache keys must vary by the negotiated value. Cache::remember on a controller action is the classic way to serve French HTML to an English visitor. Push the value into the key and set an appropriate Vary header at the edge.
  • Pre-1.0 churn. v0.1.0 is the second feature drop in the package's life; the config schema and resolver API can change between patch releases. Pin exact versions in composer.json and read the diff before bumping.
  • Debugging. When negotiation seems to ignore your configuration, log the resolver chain and which resolver produced the final value — "which source won" is the question you'll ask every time, and the package is unlikely to narrate it for you at this version. A temporary X-Negotiated-* response header in local env is worth the five minutes.
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.
frankyso/ipaymu
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