Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Cleanup Data Laravel Package

t-labs-co/laravel-cleanup-data

A Laravel package for cleaning up data in your application, helping you remove stale or unwanted records through configurable cleanup logic. Useful for pruning old data and keeping database tables tidy.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the first tagged release (v1.0.0) of t-labs-co/laravel-cleanup-data, so treat the public API as provisional — pin it ("t-labs-co/laravel-cleanup-data": "^1.0") and read the CHANGELOG link in the release notes before bumping.

  1. Install and publish config

    composer require t-labs-co/laravel-cleanup-data
    php artisan vendor:publish --tag=cleanup-data-config
    
  2. Read config/cleanup-data.php first. It is the source of truth for what the package considers a "cleanup target" — retention windows, chunk sizes, connection selection, and whether deletes are soft, force, or anonymising. Most confusion in a v1 package comes from assuming behaviour the config already decides.

  3. Register one model as a trial. Start with the safest, most reversible case you have: soft-deleted rows older than N days on a low-traffic table (e.g. notifications, password_resets, jobs, draft records). Do not start on anything containing billing or audit data.

  4. Run the discovery/dry-run pass before enabling anything.

    php artisan cleanup:data --dry-run
    

    Confirm the reported row counts match a manual SELECT COUNT(*) with the same where clause. If they diverge, your retention column or timezone assumption is wrong — fix that before scheduling.

  5. Only then wire the scheduler and leave it running in staging for a full retention cycle before production.

Implementation Patterns

Declarative rules in config vs. runtime registration. The common shape for packages like this is a per-model rule (model class + retention column + duration + action). Prefer config for rules that are stable, and a service provider if you need environment-driven windows — but keep one style per project, because debugging "why did this row get deleted" is much harder when rules are assembled from two places.

Separate "delete" from "anonymise". Model cleanup usually needs two distinct intents:

  • Purge — the row has no residual value (sessions, jobs, failed imports, temp exports).
  • Anonymise — the row matters for reporting but the PII must go (orders, support tickets, event logs). Null out or hash named columns, keep the primary key and aggregate fields.

Mixing these in one sweep is the most common cause of "we lost the data we needed to reconcile last quarter".

Schedule with overlap protection. Cleanup is idempotent but not concurrency-safe against itself on multi-server deployments:

$schedule->command('cleanup:data')->dailyAt('03:15')->onOneServer()->withoutOverlapping();

onOneServer() matters if you run more than one app node; without it you get duplicate deletes and noisy deadlock logs.

Chunk your deletes. Large single-statement deletes take long locks and balloon the binlog. Prefer a bounded, repeated delete (LIMIT + loop, or the package's chunk setting if it exposes one) over DELETE FROM ... WHERE created_at < ? on a big table. Run the first production sweep during a low-traffic window regardless of chunk size.

Use model events for the audit trail. If the package deletes via Eloquent, deleting/deleted hooks are your cheapest observability: log the model, primary key and the rule name that matched. If it deletes via the query builder, events will not fire — check which one applies before relying on them.

Test with time travel, not fixtures.

$this->travelTo(now()->addDays(31));
$this->artisan('cleanup:data')->assertSuccessful();
$this->assertDatabaseMissing('drafts', ['id' => $draft->id]);

Boundary tests (exactly N days old, one hour either side) are where off-by-one retention bugs live. Also assert that fresh rows and rows soft-deleted-then-restored survive.

Integration tip — chain it behind your backup. Schedule cleanup after the nightly database dump, never before, so you always have a pre-cleanup restore point. If the package supports a --pretend/dry-run mode, log that output somewhere grep-able each run; it doubles as a change-detection signal.

Gotchas and Tips

First release means provisional API. v1.0.0 with an empty changelog history is a promise, not a track record. Pin the exact minor, watch the CHANGELOG link, and avoid extending internal classes or overriding undocumented methods — subclassing Cleanup* internals is the fastest way to get stuck on a patch release.

Force deletes are irreversible and the package can't warn you. Confirm which of these your rule actually triggers: delete() (soft), forceDelete(), truncate(), or a raw DELETE. If foreign keys have ON DELETE CASCADE, one cleanup rule can silently remove rows in three other tables. Check information_schema.REFERENTIAL_CONSTRAINTS before your first production run.

Timezone drift. Retention comparisons against created_at use the application timezone while the DB stores UTC (or vice versa). A one-hour offset means the boundary shifts daily and rows sometimes survive a full cycle. Test with a non-UTC APP_TIMEZONE and verify against a raw SQL count.

Column and index quirks. A retention column that isn't indexed turns every sweep into a full table scan — on a large table that's a self-inflicted outage. Also watch for nullable timestamps: WHERE created_at < ? skips NULL rows entirely, so records that were never stamped will never be cleaned up and will quietly accumulate.

Config cache. php artisan config:cache in deploy means config edits need a re-cache; a rule you "added" will silently not run until you do. Same for queue workers — restart them after any deploy that changes cleanup config or rules.

Debugging a "wrong" deletion: reproduce with --dry-run plus the rule name in the output, then run the equivalent SELECT by hand. If the package logs the matched rule, correlate against your deleting event log. Disable the scheduler entry (->skip() or comment it) rather than changing retention windows when you need to stop the bleeding — narrower windows still delete, just less.

Extension points worth preferring: published config, model event listeners and the scheduler command are stable surfaces. Custom rule classes are the natural extension point if the package documents one — if it doesn't, register a small scheduled command of your own for the odd case instead of forking.

Keep a kill switch. A single env-driven boolean that short-circuits the schedule is worth ten minutes of setup and has saved more data than any backup policy.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin