Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Deleted Models Laravel Package

spatie/laravel-deleted-models

Automatically copy deleted Eloquent model attributes into a deleted_models table to create a recycle bin for records. Restore deleted models by ID when needed. An alternative to soft deletes for preserving deletion history without keeping rows in place.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-deleted-models
    

    Publish the migration (creates deleted_models table):

    php artisan vendor:publish --provider="Spatie\DeletedModels\DeletedModelsServiceProvider"
    php artisan migrate
    
  2. Enable for a Model: Add use \Spatie\DeletedModels\HasDeletedModels; to your model and implement the trait:

    use Spatie\DeletedModels\HasDeletedModels;
    
    class BlogPost extends Model
    {
        use HasDeletedModels;
    }
    
  3. First Use Case: Delete a model to trigger automatic archival:

    $post = BlogPost::find(1);
    $post->delete(); // Copies to `deleted_models` with `deleted_at` timestamp
    

Implementation Patterns

Core Workflows

  1. Soft Deletes + Archival: Combine with Laravel’s SoftDeletes for dual-layer recovery:

    use Illuminate\Database\Eloquent\SoftDeletes;
    use Spatie\DeletedModels\HasDeletedModels;
    
    class BlogPost extends Model
    {
        use SoftDeletes, HasDeletedModels;
        protected $dates = ['deleted_at', 'archived_at'];
    }
    
    • delete() → Soft deletes + archives to deleted_models.
    • forceDelete() → Permanently deletes (bypasses archival).
  2. Restoration:

    // Restore from archive (creates new record in original table)
    $restored = BlogPost::restore(1);
    
    // Restore soft-deleted (if using SoftDeletes)
    $restored = BlogPost::withTrashed()->find(1)->restore();
    
  3. Querying Archived Models: Use the DeletedModel facade or query the deleted_models table directly:

    use Spatie\DeletedModels\Facades\DeletedModel;
    
    $archived = DeletedModel::for(BlogPost::class)->get();
    

Integration Tips

  • Scopes: Add a scope to filter archived models in queries:
    public function scopeArchived($query)
    {
        return $query->whereHas('deletedModels');
    }
    
  • Events: Listen for deleted events to log or notify:
    BlogPost::deleted(function ($model) {
        // Custom logic (e.g., audit logs)
    });
    
  • API Endpoints: Expose restoration via API:
    Route::post('/posts/{id}/restore', function (BlogPost $post) {
        return $post->restore();
    });
    

Gotchas and Tips

Pitfalls

  1. Foreign Key Constraints:

    • Archival copies do not maintain database relationships. Use application logic to handle related models (e.g., cascade deletes before archival).
  2. Duplicate IDs on Restoration:

    • Restoring a model creates a new record with the same ID. If your app relies on ID uniqueness (e.g., URLs), handle conflicts:
      $restored = BlogPost::restore(1)->setAttribute('slug', uniqid());
      
  3. Mass Deletion:

    • Model::destroy([1, 2, 3]) archives each model individually. For bulk archival, use:
      BlogPost::whereIn('id', [1, 2, 3])->get()->each->delete();
      
  4. Custom Columns:

    • Only serializable attributes are archived. Exclude sensitive data:
      protected $hidden = ['api_token'];
      protected $casts = ['password' => 'encrypted'];
      

Debugging

  • Missing Archives: Verify the deleted_models table exists and the trait is applied.
  • Restore Failures: Check for duplicate ID conflicts or validation errors.
  • Performance: Large archives may slow queries. Add indexes:
    Schema::table('deleted_models', function (Blueprint $table) {
        $table->index(['model_type', 'model_id']);
    });
    

Extension Points

  1. Custom Archive Table: Override the default table name in config/deleted-models.php:

    'table_name' => 'custom_deleted_models',
    
  2. Archive Logic: Extend the archive method in the trait:

    protected function archive()
    {
        $this->setAttribute('archived_reason', 'manual cleanup');
        parent::archive();
    }
    
  3. Soft Delete Override: Disable soft deletes for archival-only behavior:

    use HasDeletedModels;
    
    class BlogPost extends Model
    {
        use HasDeletedModels;
        // No SoftDeletes trait
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport