{"id":"39861b2b28dc9c41","repo":"laravel/framework","slug":"please-implement-the-prunable-method-on-your-model","errorCode":null,"errorMessage":"Please implement the prunable method on your model.","messagePattern":"Please implement the prunable method on your model\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Illuminate/Database/Eloquent/MassPrunable.php","lineNumber":50,"sourceCode":"\n            if ($count > 0) {\n                event(new ModelsPruned(static::class, $total));\n            }\n        } while ($count > 0);\n\n        return $total;\n    }\n\n    /**\n     * Get the prunable model query.\n     *\n     * @return \\Illuminate\\Database\\Eloquent\\Builder<static>\n     *\n     * @throws \\LogicException\n     */\n    public function prunable()\n    {\n        throw new LogicException('Please implement the prunable method on your model.');\n    }\n}\n","sourceCodeStart":32,"sourceCodeEnd":53,"githubUrl":"https://github.com/laravel/framework/blob/bd6b5437e6ad87bb49f9b426724f07a9f64e9683/src/Illuminate/Database/Eloquent/MassPrunable.php#L32-L53","documentation":"The MassPrunable trait provides a default prunable() method that only throws this LogicException. MassPrunable deletes records in bulk via a single query (chunked delete) and requires the consuming model to override prunable() to return the query builder that selects which records to remove. Without an override, pruneAll() cannot proceed.","triggerScenarios":"Adding 'use MassPrunable;' to a model and then invoking Model::pruneAll() (typically via the model:prune artisan command) without overriding the prunable() method.","commonSituations":"Adopting the MassPrunable trait for faster pruning but forgetting the prunable() stub, or swapping from Prunable to MassPrunable and missing the override requirement.","solutions":["Implement prunable() on the model returning a Builder, e.g. public function prunable() { return static::where('created_at', '<', now()->subYear()); }.","If you intended per-model pruning logic, use the Prunable trait instead of MassPrunable.","Ensure the method returns a Builder (not a collection) and includes the where conditions selecting rows to prune."],"exampleFix":"// before\nclass LogEntry extends Model {\n    use MassPrunable;\n}\n\n// after\nclass LogEntry extends Model {\n    use MassPrunable;\n\n    public function prunable()\n    {\n        return static::where('created_at', '<', now()->subMonth());\n    }\n}","handlingStrategy":"type-guard","validationCode":"$reflection = new \\ReflectionMethod($modelClass, 'prunable');\nif ($reflection->getDeclaringClass()->getName() === \\Illuminate\\Database\\Eloquent\\MassPrunable::class) {\n    throw new \\LogicException(\"{$modelClass} must override prunable() before pruning.\");\n}","typeGuard":"function implementsPrunable(string $modelClass): bool {\n    return (new \\ReflectionMethod($modelClass, 'prunable'))\n        ->getDeclaringClass()->getName() !== \\Illuminate\\Database\\Eloquent\\MassPrunable::class;\n}","tryCatchPattern":null,"preventionTips":["Always override prunable() the moment you add 'use MassPrunable;'.","Run model:prune in a non-prod environment first to surface missing overrides.","Document the override requirement in the trait adoption checklist."],"tags":["eloquent","pruning","maintenance","artisan"],"analyzedSha":"bd6b5437e6ad87bb49f9b426724f07a9f64e9683","analyzedAt":"2026-08-06T00:28:32.783Z","schemaVersion":2}