spatie/laravel-directory-cleanup
Automatically delete old files from specified directories in Laravel. Configure per-path age limits (in minutes) via a published config file, then run cleanup to keep temp, cache, and upload folders tidy. Supports auto service provider registration in Laravel 5.5+.
Installation:
composer require spatie/laravel-directory-cleanup
Publish the config file:
php artisan vendor:publish --provider="Spatie\DirectoryCleanup\DirectoryCleanupServiceProvider"
Configure Directories:
Edit config/directory-cleanup.php to define directories and their retention rules:
'directories' => [
'storage/logs' => [
'max_age_in_days' => 30,
'dry_run' => false,
],
'storage/framework/cache' => [
'max_age_in_days' => 7,
'dry_run' => false,
],
],
First Run:
Schedule the cleanup in app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('directory:cleanup')->daily();
}
Run manually:
php artisan directory:cleanup
Clean up old logs and cache files automatically. Start with dry_run: true to preview deletions before enabling actual cleanup.
Directory-Specific Cleanup: Use the package to target specific directories (e.g., logs, uploads, backups) with custom retention policies:
'directories' => [
'storage/app/public/uploads' => [
'max_age_in_days' => 90,
'only' => ['*.jpg', '*.png'], // Optional: Filter file extensions
],
],
Dynamic Configuration:
Override config values per environment (e.g., config/directory-cleanup.php):
if (app()->environment('production')) {
$config['directories']['storage/logs']['max_age_in_days'] = 7;
}
Manual Triggers: Call the cleanup programmatically (e.g., in a controller or command):
use Spatie\DirectoryCleanup\DirectoryCleanup;
DirectoryCleanup::cleanup();
Event-Based Cleanup:
Hook into Laravel events (e.g., job.failed) to trigger cleanup:
Event::listen('job.failed', function () {
DirectoryCleanup::cleanup(['storage/logs']);
});
try-catch block and log failures:
try {
DirectoryCleanup::cleanup();
} catch (\Exception $e) {
Log::error('Directory cleanup failed: ' . $e->getMessage());
}
dry_run: true in tests to verify behavior without side effects:
$this->artisan('directory:cleanup')->expectsOutput('Would delete...');
php artisan make:command CustomCleanupCommand
public function handle() {
DirectoryCleanup::cleanup(['custom/path']);
}
Permissions:
Ensure the Laravel process has write permissions for target directories. Use chmod or chown if needed:
chmod -R 755 storage/
Debug: Check logs for Permission denied errors.
Symbolic Links: The package follows symlinks by default. Disable with:
'follow_symlinks' => false,
Tip: Test with dry_run: true first to avoid unintended deletions.
Concurrent Runs: Avoid running cleanup multiple times simultaneously (e.g., via cron + scheduled task). Use a lock:
if (!file_exists(storage_path('directory_cleanup.lock'))) {
file_put_contents(storage_path('directory_cleanup.lock'), 'locked');
DirectoryCleanup::cleanup();
unlink(storage_path('directory_cleanup.lock'));
}
Hidden Files:
The package skips hidden files (e.g., .env). To include them, override the isHidden() method in a custom cleaner:
use Spatie\DirectoryCleanup\Cleaners\Cleaner;
class CustomCleaner extends Cleaner {
public function isHidden(string $path): bool {
return false;
}
}
dry_run: true during development to preview deletions:
'dry_run' => env('APP_ENV') !== 'production',
'debug' => true,
tail -f storage/logs/laravel.log | grep "DirectoryCleanup"
Custom Cleaners:
Extend the base Cleaner class to add logic (e.g., exclude specific files):
class ExcludeTempCleaner extends Cleaner {
public function shouldBeDeleted(string $path): bool {
return parent::shouldBeDeleted($path) && !Str::contains($path, 'temp_');
}
}
Register in config/directory-cleanup.php:
'cleaners' => [
\App\Cleaners\ExcludeTempCleaner::class,
],
Pre/Post Hooks: Use Laravel events to run logic before/after cleanup:
Event::listen('directory.cleanup.starting', function () {
Log::info('Starting directory cleanup...');
});
Event::listen('directory.cleanup.finished', function () {
Log::info('Directory cleanup completed.');
});
Dynamic Retention:
Fetch max_age_in_days from a database or API:
$config['directories']['storage/logs']['max_age_in_days'] = Setting::get('log_retention_days');
How can I help you explore Laravel packages today?