Download the PHP package graystackit/laravel-gdpr-compliance without Composer
On this page you can find all versions of the php package graystackit/laravel-gdpr-compliance. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download graystackit/laravel-gdpr-compliance
More information about graystackit/laravel-gdpr-compliance
Files in graystackit/laravel-gdpr-compliance
Package laravel-gdpr-compliance
Short Description GDPR/DSGVO compliance toolkit for Laravel applications: consent, data export, erasure, legal hold, audit logging and package inventory.
License MIT
Homepage https://github.com/graystackit/laravel-gdpr-compliance
Informations about the package laravel-gdpr-compliance
Laravel GDPR Compliance
A comprehensive GDPR/DSGVO compliance toolkit for Laravel applications. Declare personal data on your Eloquent models, manage consent, export subject data, schedule erasure with grace periods and legal hold, and maintain a tamper-evident audit trail — all through a fluent PHP API.
Features
- Fluent personal data declaration on any Eloquent model via a builder DSL
- Registry-driven processing — one config array lists all PII-holding models; no relation graph to maintain
- Subject data export (DSGVO Art. 15) as a structured JSON file, dispatched as a queue job
- Subject data erasure (DSGVO Art. 17) with configurable grace period, three retention modes (
delete,anonymize,legal_hold), and deterministic processing order for FK safety - Per-purpose consent management — append-only
consentstable, cookie consent helper, middleware - Policy version tracking with subject acceptance records
- Event-driven audit log (
gdpr_audits) that never stores PII values — only field names, event names, and metadata - 7 built-in anonymizers (name, email, phone, IP address, address, free text, static text) with custom alias support
- Package inventory scanner that reads
composer.lock+package-lock.jsonand writes a JSON snapshot - 4 Laravel Notifications for deletion requested/cancelled/completed and export ready — overridable via config
- 3 middleware for consent-gated routes, cookie propagation, and deletion-pending auth blocking
- 8 Artisan commands for daily operations, reporting, pruning, and inventory scanning
- 3 queue jobs for async export, deletion processing, and legal hold cleanup
Requirements
- PHP 8.3+
- Laravel 11, 12, or 13
Installation
Publish the config and migrations:
Optionally publish translations for customization:
Quick Start
1. Declare personal data on your models
Every model that holds personal data implements PersonalData and uses one or more GDPR traits:
Related models (that are NOT subjects) use HasPersonalData and define a scope:
2. Register models in config
In config/gdpr.php, list every model that contains personal data:
3. Use the API
Trait Reference
| Trait | Who uses it | What it provides |
|---|---|---|
HasPersonalData |
Any model with PII (User, Order, Address, ...) | Marker trait. No runtime behavior — the package reads personalData() via the registry. |
IsPersonalDataSubject |
Only subjects (User, Customer, ...) | requestDeletion(), deleteImmediately(), cancelDeletion(), requestExport(), isDeletionPending(), scopeWhereDeletionPending(), scopeWhereNotDeletionPending() |
HasConsentRecords |
Subjects that need consent tracking | grantConsent(), withdrawConsent(), hasConsent(), consentStatus(), consents() relationship |
Per-Field DSL
Both behaviors are opt-in per field:
| Call | Effect |
|---|---|
->field('x')->anonymizeWith('alias') |
Anonymize only |
->field('x')->exportable() |
Export only |
->field('x')->anonymizeWith('alias')->exportable() |
Both |
->field('x') (nothing further) |
Throws on build() — the field is functionless |
Built-in anonymizer aliases
| Alias | Class | Behavior |
|---|---|---|
name |
NameAnonymizer |
Replaces with "Anonymous User" (configurable via placeholder) |
email |
EmailAnonymizer |
Replaces with anonymized_<random>@example.invalid (configurable domain) |
phone |
PhoneAnonymizer |
Replaces with +00 000 0000000 (configurable placeholder) |
ip_address |
IpAddressAnonymizer |
IPv4: masks last octet by default (octet), or half or full. IPv6: keeps first 4 groups. |
address |
AddressAnonymizer |
Strings → [REDACTED ADDRESS]. Arrays → each value [REDACTED]. |
free_text |
FreeTextAnonymizer |
Full replacement by default. Set replace_email, replace_phone, replace_urls to selectively replace patterns. |
static_text |
StaticTextAnonymizer |
Returns config.value (default [REDACTED]). |
Register custom anonymizers in config/gdpr.php:
Your class must implement GraystackIt\Gdpr\Contracts\Anonymizer.
Retention Modes
Configured per model via ->retention():
| Mode | After grace | Terminal state |
|---|---|---|
delete |
Row is hard-deleted | erased |
anonymize |
Fields wiped via anonymizers, row stays | anonymized |
legal_hold |
Fields wiped, row retained until hold_until |
pending_legal_hold → erased after expiry |
Processing order
->processOrder(int) controls the sequence when multiple models are processed for the same subject. Lower numbers go first. Convention:
| Range | Use |
|---|---|
| 1–99 | Pivot/junction tables |
| 100–199 | Direct children (Order, Address, Comment) |
| 200–299 | Indirect children (LoginAttempt, Metrics) |
| 1000 | The subject itself |
This prevents FK constraint violations when children reference the subject with NOT NULL foreign keys.
Deletion Lifecycle
During the grace period, nothing is modified on host model rows. The entire grace state lives in the gdpr_deletions table. This means:
- Cancellation is trivial — just flip the state
- The package never forces
SoftDeleteson your models - Auth behavior during grace is your app's decision (see below)
Auth during grace
The package does not lock users out during grace. Use these helpers to implement your preferred UX:
Consent Management
Database consent (authenticated users)
The consents table is append-only. Each grant and withdraw is a new row. The current state is the latest row per (subject, purpose).
ConsentPurpose::Necessary always returns true without any database check.
Cookie consent (anonymous visitors)
The ConsentCookieManager reads/writes a JSON cookie (gdpr_consent) with per-purpose booleans:
Consent middleware
Audit Log
The gdpr_audits table records deletion/export pipeline events only. It never stores:
- Field values (only field names)
- Raw user agents or full IP addresses
- Consent grants/withdrawals (those live in
consents) - Policy acceptances (those live in
gdpr_policy_acceptances)
After a subject is hard-deleted, their audit entries survive as orphans — the subject_id FK points nowhere, which means no re-identification is possible. This is by design.
Logged events: deletion_requested, deletion_scheduled, deletion_cancelled, anonymization_completed, deletion_completed, legal_hold_started, legal_hold_expired, export_requested, export_completed.
Events
The package fires these events for external system integration (e.g., deleting Stripe customers, removing Mailchimp subscribers):
| Event | Payload | When |
|---|---|---|
PersonalDataDeletionRequested |
GdprRequest |
requestDeletion() called |
PersonalDataDeletionCancelled |
GdprRequest |
cancelDeletion() called |
PersonalDataAnonymized |
GdprDeletion |
After fields wiped on a model |
PersonalDataErased |
GdprDeletion |
After row hard-deleted |
LegalHoldStarted |
GdprDeletion |
Row enters legal hold |
LegalHoldExpired |
GdprDeletion |
Row exits legal hold (force-deleted) |
PersonalDataExported |
GdprRequest |
Export job completed |
Notifications
Four mail notifications are sent automatically (when config('gdpr.notifications.enabled') is true):
| Notification | When | Final? |
|---|---|---|
PersonalDataDeletionRequestedNotification |
On requestDeletion() |
No |
PersonalDataDeletionCancelledNotification |
On cancelDeletion() |
Yes (email wiped) |
PersonalDataDeletionCompletedNotification |
After processing | Yes (email wiped) |
PersonalDataExportReadyNotification |
After export job | Yes (email wiped) |
The recipient email is snapshotted into gdpr_requests.notification_email at request time, so notifications work even after the subject's data has been anonymized or deleted. After the final notification, the email is wiped.
Customizing notifications
Text only: publish translations with php artisan vendor:publish --tag=gdpr-lang and edit lang/vendor/gdpr/en/gdpr.php.
Deep customization: override the class in config/gdpr.php:
Artisan Commands
| Command | Purpose |
|---|---|
gdpr:process-deletions |
Run daily via scheduler. Processes grace-expired and legal-hold-expired rows. |
gdpr:export {subject} {id} |
Create an export request and dispatch the export job. |
gdpr:erase {subject} {id} [--now] |
Request deletion. --now skips grace. |
gdpr:audit [--subject=] [--id=] [--event=] |
Show recent audit entries with filters. |
gdpr:report |
Summary of requests, deletions, consent counts, audit counts. |
gdpr:packages-scan |
Scan composer.lock + package-lock.json and write inventory JSON. |
gdpr:cleanup-exports [--disk=local] |
Delete expired export files from storage. |
gdpr:prune [--dry-run] [--table=] |
Time-based pruning of audits, consents, policy acceptances, and stale notification emails. |
Scheduling
Add to your routes/console.php or scheduler:
Package inventory
Wire the scanner into your host app's composer.json:
Access the inventory programmatically:
Pruning & Retention
The gdpr:prune command handles time-based cleanup:
| Table | Default retention | Special rules |
|---|---|---|
gdpr_audits |
3 years (1095 days) | — |
consents |
3 years | Latest row per (subject, purpose) is always preserved |
gdpr_policy_acceptances |
3 years | — |
gdpr_requests.notification_email |
7 days after terminal status | Wiped to NULL, row itself retained for 3 years |
Configure in config/gdpr.php:
Database Tables
| Table | Purpose |
|---|---|
consents |
Append-only consent records (grant/withdraw) per subject and purpose |
gdpr_requests |
Top-level request lifecycle (export/delete), email snapshot |
gdpr_deletions |
One row per (request x affected model), retention snapshot, state machine, process_order |
gdpr_audits |
Event-driven audit log for the deletion/export pipeline |
gdpr_policy_versions |
Policy version definitions (privacy, imprint, ToS) |
gdpr_policy_acceptances |
Subject acceptance records per policy version |
GDPR Compliance Notes
Anonymization vs. pseudonymization
The anonymize mode replaces personal field values with non-identifying placeholders. Whether the result qualifies as true anonymization (GDPR no longer applies) or pseudonymization (GDPR still applies) depends on which fields you configure.
To achieve proper anonymization, ensure you mark all identifying fields — including quasi-identifiers:
- Birthdate + ZIP + gender can uniquely identify 87% of the US population (Sweeney 2000)
- Behavioral patterns in related tables (order timestamps, login times) may re-identify subjects
- External datasets can be joined to remaining data points
The package gives you the tooling. Field selection is your responsibility.
Grace period
- Default
0days (immediate processing). Opt in per model. - Hard cap at 30 days (DSGVO Art. 12(3): "without undue delay, in any event within one month").
- During grace, no host model data is modified — cancellation is a clean rollback.
Legal hold
- Enabled via
RetentionMode::LegalHoldwithlegalHoldDays. - The
legalBasisfield is optional but recommended — the audit log records it for compliance evidence. - After
hold_untilexpires, the row is mandatorily force-deleted by the cron command.
Backups
The package cannot reach into backup files. If you restore from a backup, pending deletion requests should be re-applied. Document your backup retention in your privacy policy and ensure backups rotate within a documented window.
Subject-to-subject references
When processing Subject A, the package never modifies Subject B — even if B has a foreign key to A. Use onDelete('set null') on FK migrations or listen to the PersonalDataErased event to handle cross-subject cleanup in your app code.
Publishing Reference
| Tag | What it publishes | Required? |
|---|---|---|
gdpr-config |
config/gdpr.php |
Yes |
gdpr-migrations |
database/migrations/*.php |
Yes |
gdpr-lang |
lang/vendor/gdpr/en/gdpr.php |
No — for text customization |
gdpr-notifications |
app/Notifications/*.php |
No — for deep notification customization |
gdpr |
All of the above | Convenience |
Testing
License
The MIT License (MIT). See LICENSE for details.
All versions of laravel-gdpr-compliance with dependencies
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/http Version ^11.0|^12.0|^13.0
illuminate/notifications Version ^11.0|^12.0|^13.0
illuminate/queue Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0