composer require symfinity/privacy-settings-bundleWith Flex, the recipe registers the bundle, copies default category config, and wires the consent submit route.
Add the banner to your existing layout — one component line; CSS is injected automatically:
{# templates/base.html.twig #}
<body>
<twig:ConsentBanner privacyPolicyUrl="https://example.com/privacy" />
{% block content %}{% endblock %}
</body>Equivalent: {{ component('ConsentBanner', { privacyPolicyUrl: '…' }) }} — tag and function syntax invoke the same Twig component.
Omit subjectKey for anonymous visitors. See Usage for props and kernel theming.
The Flex recipe copies a four-category default (required, analytics, marketing, media). Adjust labels and descriptions for your policy; keep ids stable so stored consent and Twig gates stay aligned.
# config/packages/symfinity_privacy_settings.yaml
symfinity_privacy_settings:
categories:
- id: required
label: Required
default_state: required
description: Essential cookies and storage for security, remembering your consent choices, and core site functionality. Always active.
- id: analytics
label: Analytics
default_state: disabled
description: Optional measurement of how the site is used — page views, performance, and errors — to help improve the experience.
- id: marketing
label: Marketing
default_state: disabled
description: Optional cookies for ad campaign measurement and showing related content on other websites.
- id: media
label: Media
default_state: disabled
description: Optional third-party embeds such as video and audio players, maps, widgets, and social media content.See Configuration for storage, enforcement, and default_state semantics.
Flex copies config/routes/symfinity_privacy_settings.yaml, which imports the consent submit route (and optional dev demo loader). Manual installs: copy that file from the package into config/routes/ or import the bundle file:
# config/routes/symfinity_privacy_settings.yaml
symfinity_privacy_settings:
resource: '@PrivacySettingsBundle/config/routes.yaml'ConsentSubmitController reads the posted privacy[…] fields, calls PreferenceCaptureService, and redirects back to the referer.
The banner records choices. v0.2 adds enforcement helpers — see Enforcement for privacy_consent() and PrivacyMediaEmbed.
Listen for ConsentDecisionEvent when a category toggles (banner save or programmatic capture):
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfinity\PrivacySettingsBundle\Event\ConsentDecisionEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
final class AnalyticsConsentListener
{
public function __invoke(ConsentDecisionEvent $event): void
{
if ('analytics' !== $event->categoryId || !$event->newState) {
return;
}
// Enable analytics scripts, tag managers, etc.
}
}Inject PreferenceRestoreService when you need the effective state for a visitor key:
use Symfinity\PrivacySettingsBundle\Consent\PreferenceRestoreService;
use Symfinity\PrivacySettingsBundle\Symfony\CategoryModelNormalizer;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
public function __construct(
private readonly PreferenceRestoreService $restoreService,
private readonly CategoryModelNormalizer $normalizer,
#[Autowire(param: 'symfinity.privacy_settings.categories')]
private readonly array $rawCategories,
) {}
public function dashboard(string $subjectKey): Response
{
$categories = $this->normalizer->normalize($this->rawCategories);
$choices = $this->restoreService->effectiveChoices($subjectKey, $categories);
// $choices['analytics'] === true|false
}See Enforcement for all Twig blocking methods (comparison table + examples).
{# 1. Server-side if — scripts, pixels, sections, manual iframes #}
{% if privacy_consent('analytics') %}
<script src="/assets/analytics.js" defer></script>
{% endif %}
{# 2. Media iframe — uses media category; facade + Load when denied #}
<twig:PrivacyMediaEmbed provider="youtube" videoId="dQw4w9WgXcQ" title="Demo video" />
{# 3. Marketing pixel (same pattern as analytics) #}
{% if privacy_consent('marketing') %}
<img src="https://example.com/pixel.gif" alt="" width="1" height="1" hidden>
{% endif %}
{# 4. Declarative script (requires enforcement.client_scripts: true) #}
<script type="text/plain" data-privacy-category="analytics" src="/assets/analytics.js"></script>ConsentBanner loads privacy-settings-bundle/styles/privacy-settings-consent.css automatically via AssetMapper — no importmap.php or app.js import required.
Declare scripts or iframes that belong to a category. Prefer privacy_consent() for Twig snippets, or enable the opt-in unblocker — see Enforcement.
<script type="text/plain" data-privacy-category="analytics" src="/assets/analytics.js"></script>See strict-attribute-contract for forbidden aliases.
Skip rendering ConsentBanner and call capture/restore services directly — see Usage.
GET /_privacy/demo is off in prod (default). It is enabled automatically in dev and test for maintainer smoke — see Verification.
Do not enable in production unless you explicitly want a public showcase page.
cd src/symfinity
./bin/php vendor/bin/phpunit packages/privacy-settings-bundle/tests/