symfinity/privacy-settings-bundle
A Symfony bundle for managing user privacy settings and consent preferences. Configure privacy options, persist choices, and integrate GDPR-style consent handling into forms, templates, and services with a simple, extensible API.
First, the reality check — do this before anything else. Despite the domain fit, this is a Symfony bundle, not a Laravel package. Open composer.json and look at require: if it lists symfony/framework-bundle, symfony/http-kernel, doctrine/orm and no illuminate/*, then:
extra.laravel.providers entry, so Composer auto-discovery does nothing.php artisan vendor:publish has no tag to publish — config lives in a Symfony Extension/Configuration tree, migrations are Doctrine migrations, and storage is a Doctrine entity, not an Eloquent model.*Bundle class in config/app.php will not work; a Symfony bundle expects a Symfony kernel/container.So the day-to-day question is not "how do I install it" but "do I adopt it as a spec, or adapt it."
Where to look first (15–20 minutes of reading):
composer.json — framework requires, PHP range, whether it's Packagist or VCS-only.DependencyInjection/Configuration.php — this is the category/purpose schema. It tells you what consent categories you can define and what the default-deny behaviour looks like.src/ — grab the FQCN; it's the one method you'll call everywhere).users table or an equivalent subject identifier.Minimal steps, if you proceed:
composer require symfinity/privacy-settings-bundle:~0.2.3
Pin the exact minor. Read CHANGELOG.md at tag v0.2.3 (the release note is only a link) and run its test suite on your PHP/Laravel matrix before writing a line of glue.
First use case to target: a single cookie/marketing consent gate. Define two or three categories in its config, map its consent record onto a table keyed by a stable subject id (user id when authenticated, a device cookie otherwise), and expose exactly one method to your app: granted($subject, 'marketing'): bool. Nothing else — no banner redesign, no CMP replacement — until fail-closed semantics are proven.
1. Wrapper-first, always. Do not let bundle classes leak into controllers, Blade, or jobs. Define a Laravel-side contract:
interface Consent
{
public function granted(?string $subject, string $category): bool;
public function record(string $subject, string $category, bool $granted, string $policyVersion, string $source = 'app'): void;
public function withdraw(string $subject, ?string $category = null): void;
public function export(string $subject): array;
}
Bind either a thin adapter over the bundle's service (if you keep the dependency) or a native implementation informed by its category schema. Every call site now survives the package being abandoned or replaced — which, given single-maintainer status and pre-1.0 versioning, you should assume will happen.
2. Resolve once per request, memoise hard. Consent lookups are per-page-view. Register the consent service as a scoped singleton and populate it from a middleware (or a queued Terminating listener) so @consent('marketing') in three partials costs one query, not three. Back the scoped instance with a short-TTL cache keyed consent:{subject} and bust it on the consent-changed event.
3. Mirror the Twig helpers as Blade directives. The bundle ships Twig; you live in Blade. Register a compiler directive that forwards to your contract:
Blade::if('consent', fn (string $category) => app(Consent::class)->granted($this->subjectKey(), $category));
// @consent('marketing') ... @endconsent
Keep the semantics identical to the package's so a future port is a swap, not a rewrite.
4. Bridge consent-change events to Laravel events. The bundle's hooks are Symfony events. If you keep it in-process, subscribe them and re-dispatch as ConsentChanged($subject, $category, $granted, $version). Listeners: cache invalidation, audit-log row, downstream CMP/vendor sync, marketing-tag suppression. This event is also your instrumentation point — log every decision with category and policy version at debug.
5. Map storage deliberately, not incidentally. Doctrine entity ≠ Eloquent model, and php artisan migrate will not create its table. Two viable paths: (a) let the bundle own the table and read/write it via DB::connection()->table(...) with a hand-written Laravel migration matching its schema, or (b) own an Eloquent ConsentRecord and dual-write while the package remains a shadow reader. Either way the record must carry, at minimum: subject id, category, granted, policy version/hash, timestamp, source/UI variant. That set is your DSAR export and your audit defence.
6. Treat a category-config change as a migration. Bumping the policy version in config means previously captured grants are stale and must be re-prompted — not silently carried forward. Ship config changes and the corresponding re-consent flow in the same release, and note it in the migration doc.
7. Backfill with an explicit "unknown/legacy" state. An artisan consent:backfill command mapping your current records into the new shape, with a documented third state so historical users are never silently reclassified as opted in. Pair it with consent:export {subject} for DSAR fulfilment — write it before you need it.
8. Roll out behind a flag with the legacy path retained. One release of coexistence (shadow-read: compute consent both ways, log mismatches, enforce the old path) before the new one becomes the system of record.
vendor:publish, no auto-discovery, no Eloquent. If you're scanning for these, stop: they don't exist here. The absence of a publishable config tag is the tell.false. Wrap every call site in try/catch → false regardless of what the package does internally. This is the one test that must exist before production traffic.^0.2 permits breaking changes on 0.3.0. Pin ~0.2.3, review every minor bump manually, and never let a dependency bot auto-merge one — this is a compliance control, not a utility library.CHANGELOG.md with no inline summary. Read the tag body and the diff before upgrading; you cannot otherwise distinguish a docs fix from a consent-evaluation bug.How can I help you explore Laravel packages today?