- 5 minutes read

Schedule periodic database clean-up on Laravel

For obvious reasons, database tables tend to grow in size as time passes. Therefore, it usually requires tuning from time to time to maintain queries performing well. Some common techniques are creating indexes, rewriting queries, and even redesigning the database.

But there is another obvious solution that could also be applied, which is pruning data. Laravel has a built-in feature for that, and can be used in two different ways: Pruning and Mass Pruning:

Pruning

Let's say you want to prune soft-deleted users after 90 days from the deletion date.

Add the Illuminate\Database\Eloquent\Prunable trait to the model and implement a prunable method which returns an Eloquent query builder that scopes the query to get the Prunable records.

app/Models/User.php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use Prunable;
    use SoftDeletes;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('deleted_at', '<=', now()->subDays(90));
    }
}

When marking models as Prunable, you may also define a pruning method on the model.
This method will be called before the model is deleted

To show an actual application for this method, let's say the user has a profile picture, and it also needs to be deleted from the file storage:

app/Models/User.php

/**
 * Prepare the model for pruning.
 */
protected function pruning(): void
{
    Storage::delete($this->picture);
}

Now you can run the model:prune Artisan command or schedule it in your application's App\Console\Kernel class.

You can set the desired interval, but In this case, we want to prune the data daily:

app/Console/Kernel.php

/**
 * Define the application's command schedule.
 */
protected function schedule(Schedule $schedule): void
{
    $schedule->command('model:prune')->daily();
}

When you run the model:prune command, it detects models within your application's app/Models that implement the Illuminate\Database\Eloquent\Prunable trait.

In case you have models in a different location, you can use the option --model:

app/Console/Kernel.php

use App\Module\Models\User;

$schedule->command('model:prune', [
    '--model' => [User::class],
])->daily();

You can also exclude a Prunable model that would be automatically detected by being in the app/Models directory using the --except option:

app/Console/Kernel.php

$schedule->command('model:prune', [
    '--except' => [AnotherModel::class],
])->daily();

Sometimes you want to know how many records would be deleted before actually doing it. This is known as a dry-run; you can do it using the --pretend option.

php artisan model:prune --pretend

Mass Pruning

While pruning retrieves the records and loops them as an eloquent object deleting them individually, which triggers the model events.

The mass pruning runs a single query, it doesn't call the pruning method, and the model events deleting and deleted.

app/Models/User.php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use MassPrunable;
    use SoftDeletes;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('deleted_at', '<=', now()->subDays(90));
    }
}

Conclusion

In this article, you've learned how to prune unnecessary data using a built-in Laravel feature and that you can delete everything at once using the Illuminate\Database\Eloquent\MassPrunable trait or record by record with the Illuminate\Database\Eloquent\Prunable trait, which allows you to define a pruning method to perform any desired action before deleting.

If you have any comments, you can share them in the discussion on Twitter.

Do you like my content?

Subscribe to my newsletter to be notified on future articles.