{"id":"4b9c97dc8d6fb337","repo":"laravel/framework","slug":"please-implement-the-prunable-method-on-your-model-4b9c97","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/Prunable.php","lineNumber":59,"sourceCode":"                    }\n                });\n\n                event(new ModelsPruned(static::class, $total));\n            });\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    /**\n     * Prune the model in the database.\n     *\n     * @return bool|null\n     */\n    public function prune()\n    {\n        $this->pruning();\n\n        return static::isSoftDeletable()\n            ? $this->forceDelete()\n            : $this->delete();\n    }\n\n    /**\n     * Prepare the model for pruning.","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/laravel/framework/blob/bd6b5437e6ad87bb49f9b426724f07a9f64e9683/src/Illuminate/Database/Eloquent/Prunable.php#L41-L77","documentation":"The Prunable trait (per-model delete-based pruning) provides a default prunable() that throws this LogicException. Unlike MassPrunable, Prunable deletes row-by-row after chunking, but still requires the model to override prunable() to return the query selecting records to prune.","triggerScenarios":"Adding 'use Prunable;' to a model and running Model::pruneAll() or model:prune without overriding prunable().","commonSituations":"Adopting Prunable for cleanup tasks and forgetting the override; switching between MassPrunable and Prunable and missing the method; artisan model:prune failing on a newly-tagged model.","solutions":["Implement public function prunable() returning a Builder that selects the records to prune.","Confirm the trait in use matches your intent (MassPrunable for bulk, Prunable for per-model pruning hooks).","Implement the pruning() hook if you also need side effects before each delete."],"exampleFix":"// before\nclass Document extends Model {\n    use Prunable;\n}\n\n// after\nclass Document extends Model {\n    use Prunable;\n\n    public function prunable()\n    {\n        return static::where('updated_at', '<', now()->subYear());\n    }\n}","handlingStrategy":"type-guard","validationCode":"$reflection = new \\ReflectionMethod($modelClass, 'prunable');\nif ($reflection->getDeclaringClass()->getName() === \\Illuminate\\Database\\Eloquent\\Prunable::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\\Prunable::class;\n}","tryCatchPattern":null,"preventionTips":["Override prunable() whenever you add 'use Prunable;'.","Run model:prune in staging first to catch missing overrides.","Implement pruning() for any per-model side effects."],"tags":["eloquent","pruning","maintenance","artisan"],"analyzedSha":"bd6b5437e6ad87bb49f9b426724f07a9f64e9683","analyzedAt":"2026-08-06T00:28:32.783Z","schemaVersion":2}