Install the Bundle:
composer require batch.com/headers-bundle
Add to config/bundles.php (Symfony 4.4+ auto-discovers, but explicit inclusion is good practice):
Batch\HeadersBundle\BatchHeadersBundle::class => ['all' => true],
Configure Headers:
Edit config/packages/batch_headers.yaml (or merge into your existing config):
batch_headers:
headers:
- X-Frame-Options: DENY # Basic example
First Use Case: Immediately enforce security headers (e.g., CSP, XSS protection) across all responses without modifying controllers. Test with:
php bin/console debug:headers # Symfony's built-in header inspector
Global Headers:
Use the YAML config for headers that apply universally (e.g., Strict-Transport-Security, X-Content-Type-Options). Example:
batch_headers:
headers:
- Referrer-Policy: strict-origin-when-cross-origin
Conditional Headers:
Leverage condition for dynamic logic (e.g., API vs. frontend routes):
- name: Access-Control-Allow-Headers
value: "Content-Type, Authorization"
condition: "request.getPathInfo() matches '^/api'"
Tip: Use Symfony’s ExpressionLanguage for complex conditions (e.g., request.attributes.get('_controller') == 'App\Controller\ApiController').
Response-Based Conditions: Target headers based on response content (e.g., caching images):
- name: Cache-Control
value: "public, max-age=31536000"
condition: "response.headers.get('Content-Type') matches '^image/'"
Environment-Specific Headers:
Override headers per environment (e.g., dev vs. prod) using Symfony’s %kernel.environment%:
- name: X-Debug-Token
value: "dev-token"
condition: "'%kernel.environment%' == 'dev'"
Event Listeners: Extend functionality by subscribing to kernel.response:
use Batch\HeadersBundle\Event\HeadersEvent;
public function onHeaders(HeadersEvent $event) {
$event->addHeader('X-Custom-Header', 'dynamic-value');
}
Register in services.yaml:
services:
App\EventListener\CustomHeaderListener:
tags:
- { name: 'kernel.event_listener', event: 'batch.headers', method: 'onHeaders' }
Laravel-Specific:
For Laravel (via Symfony Bridge), alias the bundle in config/app.php:
'aliases' => [
'BatchHeaders' => Batch\HeadersBundle\BatchHeadersBundle::class,
],
Then configure in config/batch_headers.php (Laravel’s config format).
Condition Syntax:
matches uses PCRE regex (case-sensitive). For case-insensitive, use matches '/^image/i'.request.getClientIp() in conditions—it may fail in proxied environments (use request.server.get('HTTP_X_FORWARDED_FOR') instead).Header Order: Headers are applied in YAML array order. Later entries override earlier ones if the same header is defined twice.
Performance:
ExpressionLanguage logic) add microseconds to response time. Benchmark critical paths.ExpressionLanguage caches by default).Symfony Version:
composer.json for compatibility (e.g., symfony/http-foundation:^4.4).Verify Headers: Use Symfony’s CLI tool:
php bin/console debug:headers --url="http://yoursite.com/api"
Or inspect HTTP responses with browser dev tools (Network tab).
Condition Debugging:
Enable Symfony’s ExpressionLanguage debugging:
framework:
expression_language:
debug: true
Log conditions to var/log/dev.log:
use Psr\Log\LoggerInterface;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function onHeaders(HeadersEvent $event) {
$this->logger->debug('Headers conditions evaluated', ['conditions' => $event->getConditions()]);
}
Custom Conditions:
Extend the condition parser by implementing Batch\HeadersBundle\Condition\ConditionInterface and register it as a service:
services:
App\Condition\CustomCondition:
tags: ['batch.headers.condition']
Header Providers: Dynamically fetch headers from a database or API:
use Batch\HeadersBundle\Provider\HeaderProviderInterface;
class DatabaseHeaderProvider implements HeaderProviderInterface {
public function getHeaders(): array {
return $this->db->fetch('SELECT name, value FROM headers');
}
}
Register in services.yaml:
services:
App\Provider\DatabaseHeaderProvider:
tags: ['batch.headers.provider']
Override Defaults:
Disable the bundle’s auto-registration by setting enabled: false in config, then manually add headers via event listeners.
Laravel-Specific Quirks:
Response object may not expose all Symfony methods. Use tap() to ensure compatibility:
$response->headers->set('X-Foo', 'Bar'); // Laravel
// or
$response->getHeaders()->set('X-Foo', 'Bar'); // Symfony-style
response()->headers->set() in middleware if needed.
```markdown
## Laravel-Specific Addendum
*(Since the package is Symfony-based but may be used in Laravel via Symfony Bridge)*
### Laravel Integration
1. **Installation**:
```bash
composer require batch.com/headers-bundle symfony/http-foundation
Configuration: Publish the config file (if using Laravel’s config system):
php artisan vendor:publish --tag=batch_headers_config
Edit config/batch_headers.php (Laravel’s format):
return [
'headers' => [
[
'name' => 'X-Powered-By',
'value' => 'Laravel + Symfony',
],
],
];
Middleware:
Register the bundle’s listener in Laravel’s middleware stack (app/Http/Kernel.php):
protected $middlewareGroups = [
'web' => [
// ...
\Batch\HeadersBundle\Middleware\HeadersMiddleware::class,
],
];
Dynamic Headers: Use Laravel’s service container to inject headers dynamically:
use Batch\HeadersBundle\Event\HeadersEvent;
public function onHeaders(HeadersEvent $event) {
$event->addHeader('X-User-ID', auth()->id());
}
Bind the listener in AppServiceProvider:
public function boot() {
event(new HeadersEvent($this->app['request'], $this->app['response']));
}
Response Object:
Laravel’s Response class may not support all Symfony header methods. Cast to Symfony\Component\HttpFoundation\Response if needed:
$symfonyResponse = $response->getOriginalContent();
$symfonyResponse->headers->set('X-Foo', 'Bar');
Blade Directives:
Headers added via the bundle won’t appear in {{ dd($response->headers) }} during Blade rendering. Use middleware or tap():
return response()->tap(function ($response) {
$response->headers->set('X-Custom', 'Blade-Friendly');
});
API Resources:
Headers applied to JSON responses may conflict with Laravel’s Resource metadata. Prioritize bundle headers by adding them last in middleware.
How can I help you explore Laravel packages today?