gisostallenberg/response-content-negotiation-bundle
Installation
composer require gisostallenberg/response-content-negotiation-bundle
Add to config/bundles.php:
Gisostallenberg\ResponseContentNegotiationBundle\ResponseContentNegotiationBundle::class => ['all' => true],
Basic Usage
In a controller, annotate your action with #[ResponseContentNegotiation]:
use Gisostallenberg\ResponseContentNegotiationBundle\Annotation\ResponseContentNegotiation;
#[ResponseContentNegotiation]
public function index(): array
{
return ['data' => 'example'];
}
The bundle will automatically negotiate responses based on Accept headers.
First Use Case
Return a JSON API response for Accept: application/vnd.api+json or HTML for Accept: text/html.
Explicit Format Handling
Use #[ResponseContentNegotiation] with formats to restrict supported formats:
#[ResponseContentNegotiation(formats: ['json', 'html'])]
public function show(): array
{
return ['id' => 1, 'name' => 'Test'];
}
Custom Formatters
Register custom formatters in config/packages/gisostallenberg_response_content_negotiation.yaml:
gisostallenberg_response_content_negotiation:
formatters:
csv:
class: App\Formatter\CsvFormatter
priority: 10
Dynamic Response Logic
Use #[ResponseContentNegotiation] with fallback to default to a format:
#[ResponseContentNegotiation(fallback: 'json')]
public function list(): array
{
return ['items' => []];
}
Integration with Symfony Serializer Leverage Symfony’s serializer for complex objects:
use Symfony\Component\Serializer\Annotation\Groups;
class User {
#[Groups(['default'])]
public string $name;
}
application/vnd.api+json) should be registered first.406 Not Acceptable errors.Accept headers in tests:
$client->request('GET', '/api/users', [], [], ['HTTP_ACCEPT' => 'application/json']);
Missing Accept Header
Without an Accept header, the bundle defaults to text/html. Explicitly set fallback to avoid surprises:
#[ResponseContentNegotiation(fallback: 'json')]
Caching Headers
Ensure Vary: Accept is set in responses to prevent caching issues across formats. Override the ResponseListener if needed.
Annotation Overrides Controller annotations override bundle config. Test edge cases where both are defined.
Priority Conflicts Formatters with the same priority may lead to inconsistent behavior. Assign unique priorities:
formatters:
json:
priority: 20
xml:
priority: 10
Check Negotiated Format Log the resolved format in your controller:
#[ResponseContentNegotiation]
public function index(): array
{
$format = $this->get('gisostallenberg_response_content_negotiation.negotiator')->getNegotiatedFormat();
\Log::info('Negotiated format:', ['format' => $format]);
return ['data' => 'example'];
}
Validate Headers
Use bin/console debug:container to inspect the Negotiator service and its configuration.
Custom Negotiator Override the default negotiator by binding your own service:
services:
Gisostallenberg\ResponseContentNegotiationBundle\Negotiator\NegotiatorInterface: '@app.custom_negotiator'
Event Listeners
Subscribe to response_content_negotiation.resolve events to modify negotiation logic dynamically.
Format-Specific Logic
Use #[ResponseContentNegotiation] with formats to restrict or extend supported formats per endpoint.
How can I help you explore Laravel packages today?