PHPackages                             krishnaraj/laravel-cascading-soft-deletes - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. krishnaraj/laravel-cascading-soft-deletes

ActiveLibrary[Database &amp; ORM](/categories/database)

krishnaraj/laravel-cascading-soft-deletes
=========================================

Automatically cascade both soft deletes and restores to nested relationships in Laravel.

v1.0.0(1mo ago)21↓50%MITPHPPHP ^8.2

Since Jun 14Pushed 1mo agoCompare

[ Source](https://github.com/Krishnaraj27/laravel-cascading-soft-deletes)[ Packagist](https://packagist.org/packages/krishnaraj/laravel-cascading-soft-deletes)[ RSS](/packages/krishnaraj-laravel-cascading-soft-deletes/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (4)Versions (3)Used By (0)

🌊 Laravel Cascading Soft Deletes
================================

[](#-laravel-cascading-soft-deletes)

 **Automatically cascade soft-deletes *and* restores across your Eloquent relationships — safely, precisely, and with full multi-parent support.**

 [![Packagist Version](https://camo.githubusercontent.com/61162468a16b0b008e84217420b2e43e9cbe131f010cc21038c464254c04bbc3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6b726973686e6172616a2f6c61726176656c2d636173636164696e672d736f66742d64656c657465733f7374796c653d666c61742d737175617265)](https://packagist.org/packages/krishnaraj/laravel-cascading-soft-deletes) [![PHP Version](https://camo.githubusercontent.com/7cb6e51c03bd8f9bac7dad425741162bd74d0ce060270964054394ffad785eca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545382e322d626c75653f7374796c653d666c61742d737175617265)](https://packagist.org/packages/krishnaraj/laravel-cascading-soft-deletes) [![Laravel Version](https://camo.githubusercontent.com/b8d2d04b7f8fd46aec4a07f26d15bf8cd17ef30a79c5a3d52fb04828d22f4b08/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31312532307c25323031322d7265643f7374796c653d666c61742d737175617265)](https://packagist.org/packages/krishnaraj/laravel-cascading-soft-deletes) [![License](https://camo.githubusercontent.com/422db9fd40f5831c765cf6530b6750c081b696bd18d904cf89554df98c676277/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e3f7374796c653d666c61742d737175617265)](LICENSE)

---

✨ Why this package?
-------------------

[](#-why-this-package)

Laravel's built-in `SoftDeletes` trait only soft-deletes the model you call `delete()` on. Child models in `HasOne`, `HasMany`, or `BelongsToMany` relationships are left untouched — leading to orphaned records, broken UI states, and restore operations that silently bring back incomplete data.

**Laravel Cascading Soft Deletes** solves all of that:

ProblemSolutionChildren not deleted when parent is soft-deleted✅ Cascade soft-deletes through any relationship depthChildren not restored when parent is restored✅ Cascade restores — tracked precisely at the DB levelRestoring a child that was already independently deleted✅ Smart tracking skips independently-deleted childrenMultiple parents holding the same child deleted✅ Multi-parent guard prevents premature restoreForce-deleting a parent leaving orphaned children✅ Cascade force-deletes with tracking cleanupMany-to-many pivot records left after parent deletion✅ Auto-detach `BelongsToMany` on delete---

📋 Requirements
--------------

[](#-requirements)

DependencyVersionPHP`^8.2`Laravel`11.x` or `12.x`---

🚀 Installation
--------------

[](#-installation)

### 1. Install via Composer

[](#1-install-via-composer)

```
composer require krishnaraj/laravel-cascading-soft-deletes
```

### 2. Run Migrations

[](#2-run-migrations)

The package includes a migration that creates the `cascade_deletions` tracking table. Run it with:

```
php artisan migrate
```

> The migration is loaded automatically — no need to publish it unless you want to customise the schema.

### 3. (Optional) Publish the Config File

[](#3-optional-publish-the-config-file)

```
php artisan vendor:publish --tag=cascading-soft-deletes-config
```

This copies `config/cascading-soft-deletes.php` into your application for customisation.

### 4. (Optional) Publish the Migration

[](#4-optional-publish-the-migration)

If you prefer to manage the migration yourself (e.g., to rename the table):

```
php artisan vendor:publish --tag=cascading-soft-deletes-migrations
```

---

⚙️ Basic Setup
--------------

[](#️-basic-setup)

### Step 1 — Add `SoftDeletes` and `CascadesSoftDeletes` to your model

[](#step-1--add-softdeletes-and-cascadessoftdeletes-to-your-model)

```
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Krishnaraj\LaravelCascadingSoftDeletes\Traits\CascadesSoftDeletes;

class User extends Model
{
    use SoftDeletes, CascadesSoftDeletes;

    // List the relationships you want to cascade
    protected $cascadeRelationships = ['posts', 'profile'];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }
}
```

### Step 2 — That's it!

[](#step-2--thats-it)

```
$user->delete();    // Soft-deletes user, posts, and profile
$user->restore();   // Restores user, posts, and profile
$user->forceDelete(); // Permanently deletes user, posts, and profile
```

---

🔗 Relationship Support
----------------------

[](#-relationship-support)

### `HasOne` and `HasMany`

[](#hasone-and-hasmany)

These are the primary cascadeable relationships. When the parent is deleted, all related children are soft-deleted. When the parent is restored, only children that were cascade-deleted (not independently deleted) are restored.

```
protected $cascadeRelationships = ['posts', 'profile', 'addresses'];
```

### Nested (Deep) Relationships

[](#nested-deep-relationships)

Use dot-notation to cascade through multiple levels:

```
// User → Posts → Comments
protected $cascadeRelationships = ['posts.comments'];
```

When `User` is deleted:

- `User` is soft-deleted
- `Post` records are soft-deleted
- `Comment` records under each post are soft-deleted

When `User` is restored, the full chain is restored in reverse.

```
// Even deeper — 3 levels (the default max)
protected $cascadeRelationships = ['posts.comments.replies'];
```

### `BelongsToMany` (Many-to-Many)

[](#belongstomany-many-to-many)

Pivot records are **detached** (removed from the pivot table) when a parent is soft-deleted. Since pivot data is permanently lost, re-attaching on restore is not supported.

```
protected $cascadeRelationships = ['roles', 'tags'];

public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class);
}
```

> To keep pivot records on delete, set `detach_belongs_to_many => false` in the config or set `protected $cascadeDetachBelongsToMany = false` on the model.

---

🔄 Smart Restore Behaviour
-------------------------

[](#-smart-restore-behaviour)

The restore system uses a **database tracking table** (`cascade_deletions`) — not timestamp comparisons — to decide which children to restore. This makes it:

- **Precise:** A child deleted independently before the parent's cascade will never be restored by the parent's restore.
- **Multi-parent safe:** If two parents both cascade-deleted a shared child, that child will only be restored once **both** parents are restored.
- **Stale-safe:** If a child was force-deleted or independently restored after the cascade, stale tracking records are cleaned up automatically.

### Example: Independent Deletion Not Restored

[](#example-independent-deletion-not-restored)

```
$post->delete();   // Post deleted independently, no tracking record

$user->delete();   // User cascade-deletes only other posts (post above is already trashed)
$user->restore();  // User restored — independently-deleted post stays deleted ✅
```

### Example: Multi-Parent Guard

[](#example-multi-parent-guard)

```
$team->delete();  // Team cascade-deletes Post, tracking record created
$user->delete();  // User tries to cascade-delete Post — already trashed, no tracking entry

$user->restore(); // Post stays deleted (Team still holds it)
$team->restore(); // Post now restored ✅
```

---

⚙️ Configuration Reference
--------------------------

[](#️-configuration-reference)

After publishing, edit `config/cascading-soft-deletes.php`:

```
return [

    // The database table used to track cascade deletions
    'table_name' => 'cascade_deletions',

    // Maximum allowed nesting depth for relationship paths (default: 3)
    // e.g. 'posts.comments.replies' = depth 3
    'max_nesting_level' => 3,

    // Wrap cascade operations in a DB transaction (default: true)
    'use_transaction' => true,

    // Re-throw exceptions encountered during cascade (default: true)
    // Set to false to silently log errors instead
    'throw_on_error' => true,

    // Roll back the transaction if an error occurs (default: true)
    'rollback_on_error' => true,

    // Cascade restores to children when a parent is restored (default: true)
    'cascade_on_restore' => true,

    // Detach BelongsToMany pivot records on delete (default: true)
    'detach_belongs_to_many' => true,

];
```

---

🎛️ Per-Model Configuration
--------------------------

[](#️-per-model-configuration)

Every config option can be overridden on a per-model basis using protected properties. Model-level settings always take precedence over the global config.

```
class User extends Model
{
    use SoftDeletes, CascadesSoftDeletes;

    protected $cascadeRelationships = ['posts', 'profile'];

    // Override defaults per model:
    protected $cascadeNestingLimit       = 5;     // Allow deeper nesting for this model
    protected $cascadeUseTransaction     = true;  // Use transactions
    protected $cascadeThrowOnError       = false; // Suppress errors silently
    protected $cascadeRollbackOnError    = true;  // Roll back on error
    protected $cascadeOnRestore          = true;  // Cascade restores
    protected $cascadeDetachBelongsToMany = false; // Keep pivot records on delete
}
```

### Using a Method Instead of a Property

[](#using-a-method-instead-of-a-property)

For dynamic relationship lists, you may implement `cascadeRelationships()` as a method:

```
public function cascadeRelationships(): array
{
    return ['posts', 'profile'];
}
```

---

🔢 Nesting Limit
---------------

[](#-nesting-limit)

The default maximum nesting depth is **3 levels** (e.g. `user → post → comment → reply`). You can configure this globally or per-model.

If a relationship path exceeds the limit, a `NestingLimitExceededException` is thrown at delete time:

```
// Global
'max_nesting_level' => 5,

// Per model
protected $cascadeNestingLimit = 5;
```

---

💥 Force Delete Cascade
----------------------

[](#-force-delete-cascade)

When a model is **force-deleted**, the cascade hard-deletes all children (using `forceDelete()` if available, otherwise `delete()`). All tracking records are purged:

```
$user->forceDelete(); // Permanently removes user + all cascade children
```

---

🗃️ The `CascadeDeletion` Model
------------------------------

[](#️-the-cascadedeletion-model)

The `cascade_deletions` table is exposed as a first-class Eloquent model for inspection or debugging:

```
use Krishnaraj\LaravelCascadingSoftDeletes\Models\CascadeDeletion;

// Find all cascade records where a User is the parent
CascadeDeletion::forParent($user)->get();

// Find all cascade records where a Post is the child
CascadeDeletion::forChild($post)->get();
```

Each record contains:

ColumnDescription`parent_type`Fully-qualified class name of the parent model`parent_id`Primary key of the parent`child_type`Fully-qualified class name of the child model`child_id`Primary key of the child`created_at`When the cascade-delete occurred---

🛡️ Error Handling
-----------------

[](#️-error-handling)

### Throwing exceptions (default)

[](#throwing-exceptions-default)

By default (`throw_on_error = true`), any exception during cascading is logged **and** re-thrown, allowing your application to handle it:

```
try {
    $user->delete();
} catch (\Throwable $e) {
    // handle
}
```

### Suppressing exceptions

[](#suppressing-exceptions)

Set `throw_on_error = false` (globally or per-model) to silently log errors via Laravel's `Log::error()` without interrupting execution:

```
protected $cascadeThrowOnError = false;
```

### Transaction rollback

[](#transaction-rollback)

When `use_transaction = true` and `rollback_on_error = true` (both default), any error during cascading will roll back the entire delete operation — keeping your database consistent:

```
// If 'nonExistentRelation' throws, the whole cascade is rolled back
protected $cascadeRelationships = ['posts', 'nonExistentRelation'];
protected $cascadeRollbackOnError = true;
```

---

🧪 Testing
---------

[](#-testing)

```
composer test
```

The test suite covers:

- Basic cascade soft-delete and restore (HasOne, HasMany, nested)
- Tracking record creation and cleanup
- Independent deletion not restored
- Multi-parent cascade guard
- BelongsToMany detach and config flags
- Force delete cascade and tracking cleanup
- String/UUID primary keys
- Custom nesting limits
- Exception handling and transaction rollback

---

📦 Package Structure
-------------------

[](#-package-structure)

```
src/
├── CascadingSoftDeletesServiceProvider.php   # Auto-discovery service provider
├── Traits/
│   └── CascadesSoftDeletes.php              # Attach to your Eloquent models
├── Services/
│   └── CascadeService.php                   # Core cascade logic
├── Models/
│   └── CascadeDeletion.php                  # Eloquent model for tracking table
└── Exceptions/
    ├── InvalidRelationshipException.php
    └── NestingLimitExceededException.php
config/
└── cascading-soft-deletes.php               # Package configuration
database/migrations/
└── create_cascade_deletions_table.php       # Tracking table migration

```

---

📄 License
---------

[](#-license)

MIT © [Krishnaraj](mailto:gkrishnaraj2002@gmail.com)

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6baa7f62ea48c5b6f407e140c478b4d320c2abd0f9568b4b1f7472ac7dae770b?d=identicon)[krishnaraj](/maintainers/krishnaraj)

---

Top Contributors

[![Krishnaraj27](https://avatars.githubusercontent.com/u/78718103?v=4)](https://github.com/Krishnaraj27 "Krishnaraj27 (6 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/krishnaraj-laravel-cascading-soft-deletes/health.svg)

```
[![Health](https://phpackages.com/badges/krishnaraj-laravel-cascading-soft-deletes/health.svg)](https://phpackages.com/packages/krishnaraj-laravel-cascading-soft-deletes)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M348](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.5M2](/packages/glushkovds-phpclickhouse-laravel)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
