arifhp86/laravel-clear-expired-cache-file
A Laravel package that deletes expired cache files from your filesystem, keeping storage tidy and preventing stale cache buildup. Includes an artisan command and scheduler support for automatic cleanup of abandoned cache entries.
Architecture fit. This is an operational housekeeping utility, not an architectural component. It should sit entirely outside the request path — a console command (invoked manually or via the scheduler) that walks the file cache directory and removes entries whose TTL has elapsed. That framing is important: the ideal integration is zero runtime coupling, meaning no service-provider decorator around Laravel's FileStore, no middleware, no cache-driver swap. If the package does anything other than register a command, that's a yellow flag. The gap it fills is real: Laravel's file driver has no built-in expired-entry reclamation — cache:clear wipes everything, and there is no first-party cache:prune. For apps running CACHE_STORE=file with high key cardinality (per-user fragments, rate-limit keys, signed-URL tokens), expired files accumulate until someone manually clears the store.
Integration feasibility. Straightforward: composer require plus auto-discovery, assuming the version constraints cover your Laravel major. The January 2025 release predates Laravel 12, so treat 12.x support as unverified until you check composer.json constraints yourself. No database migrations, no config publication required (likely optional), no infrastructure changes. A competent Laravel developer can have it running against a staging cache directory in under an hour.
Technical risk. The risk is concentrated in one question: how does it determine expiry? Laravel stores the expiration timestamp inside the serialized cache payload, not in the file's mtime. A correct implementation deserializes the payload and reads expiresAt; a naive one uses filemtime() + ttl or a flat age threshold, which will delete still-valid entries and trigger a cache stampede against the database. Everything else is secondary, but the secondary risks are real:
storage/framework/cache/data/ab/cd/...). A sloppy recursive delete rooted one level too high can take out the entire storage/framework/cache tree — or worse, sibling directories.composer audit, and consider vendoring the source.Key questions. (1) Does it parse the serialized expiresAt, or infer expiry from mtime? (2) Does it honor a custom cache path / Cache::store('file') configuration, or hardcode storage/framework/cache? (3) Does it recurse correctly through the two-level hash directories and delete only leaf files? (4) Is there a --dry-run flag? (5) Is there an overlap lock (withoutOverlapping) or does it rely on us? (6) Laravel 11/12 and PHP 8.3/8.4 support? (7) Test suite and CI presence? (8) What does it print — deleted count, bytes reclaimed, duration — or is output unstructured?
Build-vs-buy note. This is plausibly 80 lines of code. Given the maintenance and trust profile, an internal command is a legitimate alternative — but only if your team will actually own it. A pinned third-party package with a clean diff is often the cheaper call.
Stack fit. Relevant only where the file cache driver is in use — the Laravel ≤10 default, common in single-server and small-footprint deployments, and the default in local/dev everywhere. Irrelevant for Redis, Memcached, DynamoDB, or database-backed stores; the schedule entry should be conditionally registered on config('cache.default') === 'file' so the job doesn't silently churn on stacks where it does nothing. Complement, not replacement, for cache:clear at deploy time — cache:clear handles correctness (stale config/routes), this handles accumulation.
Migration path. Install to a branch, run against a copy of a production cache directory (snapshot storage/framework/cache/data from a representative node), diff the surviving file set against expected live keys. If the dry-run output shows deletions of keys you know are unexpired, stop and reject the package. Only then promote: require in composer.json, add the schedule entry, ship. Rollback is a one-line schedule removal plus composer remove — genuinely low-cost, which is the main argument for trying it rather than building.
Compatibility. Verify the constraint matrix explicitly before committing: Laravel major, PHP minor, and the two-level directory layout (present since 5.8). Watch for Windows path separators if any developer or CI node runs Windows — unlikely to matter for prod but it will surface as a false "not working" report. Confirm behavior when the cache directory is empty or absent (first-boot containers) — it should exit cleanly, not throw.
Sequencing. (1) Install in dev, inspect the source — specifically the expiry logic and delete scope — before running anything. (2) Dry-run against a production-like cache copy; record candidate deletions and runtime. (3) Run for real on staging under production-like file counts; measure wall time and IO. (4) Schedule nightly, off-peak, with withoutOverlapping() and a timeout. (5) Instrument: capture deleted count, bytes reclaimed, and duration into your existing log/metric pipeline. (6) Revisit after 30 days — if the job consistently reclaims near-zero, either the cache has healthy natural turnover or the command is broken, and you need to know which.
Maintenance. Low code churn, low dependency surface, but a single maintainer and thin community. Treat it as a pinned, periodically-reviewed dependency rather than something you upgrade reflexively. Budget a quarterly check: upstream commits, open issues describing data loss, PHP/Laravel constraint drift. If the maintainer goes quiet, forking 80 lines is a contained exit.
Support. Community-only — GitHub issues, no SLA, no vendor. Your on-call owns failures. That's acceptable for a nightly housekeeping job and unacceptable if anyone is tempted to wire this into a request path or a deploy hook.
Scaling. The work is O(number of cache files) with a stat and a read per entry. At hundreds of thousands of files this is minutes of wall time and meaningful IO; at millions it may exceed a reasonable cron window. Mitigations: run off-peak, cap runtime with a timeout, and if directory size is genuinely large, prefer a batched/chunked approach or accept a partial sweep per night. On multi-node deployments the job must run on every node — it is not a shared-state operation. On ephemeral containers with per-container filesystems, the file cache dies with the container and the package is unnecessary; confirm your deployment topology before adding it.
Failure modes. Ranked by severity: (1) Deletes unexpired entries via an mtime heuristic — causes stampedes and database load spikes that look like an unrelated incident; this is the one to actively test for. (2) Over-broad delete scope — removes the cache directory or a sibling, breaking sessions or compiled views. (3) Permission or ownership errors in mixed-user deployments, producing partial sweeps. (4) Overlapping runs from a long sweep colliding with the next cron tick. (5) Silent failure — a job that errors nightly and nobody notices because output isn't captured. (6) Timeout on large directories, leaving the sweep perpetually incomplete. Monitoring should alert on non-zero exit, on duration regression, and on a deletion count that swings wildly in either direction.
Ramp-up. Trivial — a Laravel developer understands file cache semantics already, so the learning curve is "read the command's source, confirm the expiry logic, wire the schedule." Half a day end-to-end including validation. The real cost isn't ramp-up; it's the validation step in step (2) of the sequencing, which is non-negotiable and should not be compressed.
How can I help you explore Laravel packages today?