ongr/cookies-bundle
ONGR Cookies Bundle is a Symfony bundle for GDPR-friendly cookie consent: define cookie categories, render a consent banner, store visitor choices, and conditionally load analytics or tracking scripts only after users opt in.
1. Install and register. The bundle targets the Symfony 2.x/3.x kernel style, so on any modern Symfony you'll be pinning old framework versions (or forking):
composer require ongr/cookies-bundle
// app/AppKernel.php
$bundles = [
// ...
new Ongr\CookiesBundle\OngrCookiesBundle(),
];
If the constraint resolver fights you, check the composer.json in the repo for its symfony/symfony range before assuming the package is broken — it's a 2016 codebase and simply hasn't kept up.
2. Look at these three files first, in this order:
| File | Why |
|---|---|
DependencyInjection/Configuration.php |
The authoritative list of config keys — don't trust blog posts, the README drifts from the code. |
Resources/config/services.yml |
Gives you the real service IDs (manager, cookie model, listeners). Copy them verbatim. |
The Cookie model class |
Tells you which properties are mutable and what the setters are called. |
3. Declare one cookie and use it. The core idea is that a cookie is configuration, not a new Cookie(...) call scattered through controllers:
# app/config/config.yml
ongr_cookies:
cookies:
tracking:
name: tracking_id
max_age: 2592000 # 30 days
path: /
http_only: true
secure: true
// Any controller — grab the manager by its ID from services.yml
$manager = $this->get('ongr_cookies.cookie_manager');
$cookie = $manager->getCookie('tracking');
$cookie->setValue($someId);
$manager->updateCookie($cookie); // queues it for the response
First use case: tracking/consent IDs, locale preference, A/B bucket assignment — small, non-sensitive, non-authoritative values where losing the cookie is harmless.
Declare cookies centrally, read them by logical alias. The value of the bundle is that tracking is a stable internal name while tracking_id is the wire name. Controllers never hardcode cookie strings; renaming a cookie is a one-line config change.
Flush mutated cookies in a kernel.response listener. This is the pattern that makes the bundle usable day-to-day. Cookies must be attached to the Response after the controller returns, so either call the manager's update method inside a listener or wrap it yourself:
class CookieFlushListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
$this->manager->flushCookies($event->getResponse());
}
}
Once this is in place, any service can mutate a cookie model without caring about the response lifecycle — that's the whole point of "cookies as services."
Inject the manager, don't fetch from the container. Wire ongr_cookies.cookie_manager into your own services so cookie state is testable and doesn't leak container awareness into domain code.
Read via the model, not $request->cookies->get(). Mixing the two is the fastest way to get a bug where the request says one thing and the model says another. If you must read raw (e.g. in a Twig template), go through app.request.cookies and accept that you've bypassed the abstraction.
Test with the cookie jar, not headers. Functional tests:
$client->request('GET', '/');
$jar = $client->getCookieJar();
$this->assertSame('abc', $jar->get('tracking_id')->getValue());
// or assert at the header layer for expiry/flags
$cookies = $client->getResponse()->headers->getCookies();
Migration insurance. Wrap the manager in your own PreferenceStore interface with get()/set()/forget(). If (when) you migrate to native Symfony\Component\HttpFoundation\Cookie or a maintained consent library, you swap one adapter and leave call sites untouched. Given the package's age, treat this as mandatory rather than optional.
Integration tip — caching. Anything that sets a cookie makes the response uncacheable in Varnish/Symfony HttpCache. Be explicit about which cookies are set on which responses; a stray tracking write on a product listing page will silently nuke your edge cache hit rate.
It's unmaintained. Last release June 2016, four stars, zero dependents. Budget for a fork. The realistic plan is: use it to learn the model, then reimplement the ~100 lines you actually need on top of HttpFoundation.
Symfony's own Cookie API moved under it. In Symfony 3.2+, the expires constructor argument became DateTimeInterface instead of an integer timestamp, and later versions added sameSite and raw. The bundle's model predates all of that. Expect setcookie()-style integer handling and no SameSite support — which modern browsers now default to Lax, so cross-site flows will break in ways that look like session bugs. Patch the model or set the header manually.
Cookies you set are invisible until the next request. $request->cookies was populated before your code ran. If you write a preference and immediately read it back through the manager, you're reading request state, not the new value. Carry the value in a local variable for the rest of the request.
Deleting needs a matching fingerprint. To remove a cookie you must reproduce the same path and domain and send a past expiry. A delete that "doesn't work" is almost always a path/domain mismatch producing a second, differently-scoped cookie alongside the original.
max_age: 0 means session cookie, not "expire immediately." And secure: true will silently drop the cookie when you're testing over plain HTTP on localhost — check the scheme before blaming the bundle.
The container is compiled. You cannot create a cookie whose name is dynamic through the bundle; the alias must exist in YAML at build time. For per-request/dynamic names, bypass the manager and use new Cookie(...) directly for that one case.
Debugging checklist:
bin/console debug:container ongr_cookies — find the real service IDs (names differ across versions).$response->headers->all('set-cookie') — verify what actually shipped; the model mutating correctly proves nothing.StreamedResponse where headers are already sent.Extension points, cheapest first: decorate the manager service → add a kernel.response listener → subclass the Cookie model to add missing flags (SameSite, Partitioned) → fork. Don't patch the vendor directory.
How can I help you explore Laravel packages today?