neophp/cookieconsent-package

Cookie consent banner with optional database persistence for NeoPHP

Maintainers

Package info

github.com/NeoPHP-Dev/neo-cookieconsent-package

pkg:composer/neophp/cookieconsent-package

Transparency log

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.2 2026-08-08 07:50 UTC

This package is auto-updated.

Last update: 2026-08-08 07:50:56 UTC


README

A cookie consent banner for NeoPHP, aligned with GDPR/ePrivacy expectations: configurable categories, cookie-based storage for anonymous visitors, and optional database persistence — with full history — when a user is logged in.

Structure

cookieconsent-package/
├── composer.json
├── README.md
├── src/
│   ├── NeoCookieConsentPackage.php
│   ├── Controllers/
│   │   └── CookieConsentController.php
│   ├── Service/
│   │   └── ConsentManager.php
│   ├── Assets/
│   │   ├── css/cookieconsent.css
│   │   └── js/cookieconsent.js
│   └── Templates/
│       └── components/
│           └── CookieConsent.macro.html.twig
├── config/
│   └── cookieconsent.config.php
└── database/
    ├── Entity/
    │   └── CookieConsentRecord.php
    ├── Repository/
    │   └── CookieConsentRecordRepository.php
    └── Migrations/
        └── MigrationVersion_CookieConsent_1.php

How storage works

  • Cookie (always) — every consent choice is written to a browser cookie, readable instantly by JS without a server round-trip. This is the source of truth for anonymous visitors.
  • Database (optional, one row per consent given) — if you pass a $userType/$userId pair when recording consent, a full history record is also persisted. Unlike neo-twofactor-package or neo-permissions-package, there is no unique constraint here — every consent action creates a new row, so you always have proof of what was accepted and when, even across changes.

This package never knows about your authentication system — you decide, in your own code, whether and when to pass user identity to ConsentManager::recordConsent().

Installation

php bin/neo package:require neophp/cookieconsent-package --project=MyProject

Register it in the project's Config/app.config.php:

return [
    // ...
    'packages' => [
        \Vendor\NeoPHP\CookieConsentPackage\NeoCookieConsentPackage::class,
    ],
];

Run the migration:

php bin/neo database:migration:migrate --project=MyProject

Configuration

config/cookieconsent.config.php is copied once to Config/Packages/CookieConsent/cookieconsent.config.php:

<?php

declare(strict_types=1);

return [
    'categories' => [
        'necessary' => ['label' => 'Necessary', 'description' => 'Required for the site to function.', 'required' => true],
        'analytics' => ['label' => 'Analytics', 'description' => 'Helps us understand how visitors use the site.', 'required' => false],
        'marketing' => ['label' => 'Marketing', 'description' => 'Used to show relevant ads.', 'required' => false],
    ],
    'cookie_name' => 'neo_cookie_consent',
    'cookie_lifetime_days' => 365,
];

A category marked required: true is always forced to true when consent is recorded, regardless of what's submitted — it cannot be disabled by a visitor.

Usage

Display the banner (anonymous visitors, cookie only)

public function index(ConsentManager $consent): Response
{
    return $this->render('layout.html.twig', [
        'cookieCategories' => $consent->getCategories(),
    ]);
}
{% import '@CookieConsent/components/CookieConsent.macro.html.twig' as CookieConsent %}
<link rel="stylesheet" href="/packages-assets/CookieConsent/css/cookieconsent.css">

{{ CookieConsent.render(cookieCategories) }}

<script src="/packages-assets/CookieConsent/js/cookieconsent.js"></script>

The banner posts to /cookie-consent/ by default (this package's own route), which only writes the cookie — it never persists to the database, since it has no way to know who is logged in.

Gate a third-party script on consent

window.addEventListener('neo-consent-updated', (e) => {
    if (e.detail.analytics) {
        // load Google Analytics or similar now
    }
});

// or check at any time after the banner has resolved:
if (window.NeoCookieConsent?.hasConsent('analytics')) {
    // ...
}

Persisting consent for a logged-in user

Don't use the package's own /cookie-consent/ route for this — call ConsentManager yourself from your own controller, where you have access to your own auth system:

public function saveConsent(Request $request, ConsentManager $consent, AdminAuthManager $auth): JsonResponse
{
    $categories = json_decode($request->getPost('categories', '{}'), true);
    $user = $auth->user();

    $consent->recordConsent(
        $categories,
        $user !== null ? AdminUser::class : null,
        $user?->getId(),
    );

    return $this->json(['success' => true]);
}

You'd then point the macro's saveUrl parameter at your own route instead of the default:

{{ CookieConsent.render(cookieCategories, path('my.own.consent.route')) }}

Reading consent history for a user

$repo = $em->getRepository(CookieConsentRecord::class);
$history = $repo->findHistoryForUser(AdminUser::class, $userId);

foreach ($history as $record) {
    $record->getCategories();   // array<string, bool>
    $record->getConsentedAt();  // \DateTime
}

ConsentManager API

Method Purpose
recordConsent(array $categories, ?string $userType = null, ?int $userId = null) Writes the cookie, and persists to DB if user identity is provided
hasConsent(string $category): bool Reads from the cookie
hasAnyChoice(): bool Whether the visitor has made any choice yet
getCategories(): array The configured category definitions

Theming

Every visual value is a CSS custom property scoped to .cc-banner:

.cc-banner {
    --cc-accent: #6366f1;
    --cc-bg: #161923;
    --cc-border: #2d3342;
    --cc-text: #e5e7eb;
    --cc-text-muted: #9ca3af;
}

What this package does not do

  • Does not automatically block or defer third-party scripts already on your page — you gate them yourself using the JS API, as shown above
  • Does not scan your site for existing cookies/trackers
  • Not a substitute for legal review — category definitions, wording, and whether this approach satisfies your jurisdiction's requirements is your responsibility

License

MIT