Weave 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

Eloquent Cascade Laravel Package

sofa/eloquent-cascade

Sofa/eloquent-cascade adds cascading delete and update behavior to Laravel Eloquent models, letting you propagate changes through related records automatically. Configure cascades on relationships to keep parent and child data consistent without manual cleanup code.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin. The package has had no release since April 2021, so install it knowingly:

composer require sofa/eloquent-cascade

Before writing code, open vendor/sofa/eloquent-cascade/composer.json and confirm the illuminate/* constraint against your Laravel version. On Laravel 9+ you may need --ignore-platform-reqs, a fork, or to abandon it (see Gotchas).

2. Where to look first. There is no service provider to publish and no config file — the whole surface area is a trait plus a couple of protected properties on your models. Read the trait source (src/ in the vendor dir) top to bottom; it is small. Look specifically at which methods it overrides (delete(), forceDelete(), and any restore hook) and how it walks the declared relations. That, not the README, is the contract.

3. First use case. Add the trait to the parent model and list relation method names (not table names, not class names):

use Sofa\Eloquence\Cascade\CascadeSoftDeletes;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use SoftDeletes, CascadeSoftDeletes;

    // relation methods on this model that should be cascaded
    protected $cascadeDeletes = ['posts', 'comments'];
}

Each child must itself use SoftDeletes for a soft cascade to work, and must also use the cascade trait if you want the cascade to continue downward.

4. Prove it in a test immediately. Do not trust a manual delete through a controller:

$user = User::factory()->hasPosts(3)->create();
$user->delete();

$this->assertSoftDeleted('users', ['id' => $user->id]);
$this->assertSame(0, Post::where('user_id', $user->id)->count());
$this->assertSame(3, Post::withTrashed()->where('user_id', $user->id)->count());

If that test passes, you know the trait is actually wired into your model — that is the highest-value 10 minutes you will spend with this package.

Implementation Patterns

Model-level, not controller-level. Treat cascading as a data-integrity invariant. $user->delete() anywhere in the app — an artisan command, a queued job, a Nova action — produces the same result. Do not implement cascades in controllers; you will forget one path and orphan rows.

Wrap multi-row cascades in a transaction. The package deletes children one relation at a time; a failure partway through leaves the parent soft-deleted and some children untouched.

DB::transaction(function () use ($user) {
    $user->delete();
});

Multi-level trees. Each intermediate model needs the trait and its own list:

class Post extends Model
{
    use SoftDeletes, CascadeSoftDeletes;

    protected $cascadeDeletes = ['comments'];
}

Deleting the User then reaches Comment. Verify with a nested assertion — depth-two bugs are common because developers add the trait only to the root.

Hard deletes alongside soft deletes. Decide per model whether forceDelete() should cascade. A parent force-deleted while children are only soft-deleted leaves rows pointing at a missing user_id. Either cascade the force delete, or rely on real FK constraints with onDelete('cascade') — but not both for the same relation, or you get redundant work and confusing event ordering.

Restore is a separate problem. Cascading restores is convenient but order-sensitive if children have unique indexes or FKs. If the version you install supports it, test the restore path as carefully as the delete path; if it does not, write an explicit restore() wrapper on the parent that restores children in dependency order, inside a transaction.

Pivot / many-to-many. belongsToMany rows are not "deleted", they are detached. The package cascades model deletes; detaching pivot rows is your job. Handle it in a deleting observer on the parent:

static::deleting(function ($user) {
    $user->roles()->detach();
});

Large datasets. A cascade over a parent with 100k children will issue a query per model (children fire their own events). If the child has no observers you care about, consider a one-off Post::where('user_id', $id)->delete() (which does not fire per-model events — see Gotchas) or chunk the cascade in a job.

Testing pattern. Use RefreshDatabase and assert both the parent and the deepest child. Add a regression test asserting that a mass delete path you rely on does not silently skip the cascade.

Gotchas and Tips

  • Mass deletes bypass everything. User::where(...)->delete() and DB::table('users')->delete() do not instantiate models, so no events fire and no cascade runs. Always call delete() on a model instance when you need cascading. This is the single most common production surprise with this package.
  • Relation method names only. $cascadeDeletes entries must be methods that return a Relation. A typo, or listing a column, typically fails quietly or throws an unrelated error deep in the trait. Add a test per relation you list.
  • Circular relations cause infinite recursion. User → Post → User will loop if both sides declare the cascade. Guard the downward traversal or keep cascades strictly directional.
  • Do not call delete() inside a deleting observer on a model that is already part of a cascade — you will re-enter the trait and double-fire events. Use deleted for post-processing (files, search index) and keep deleting free of further deletes.
  • Unique indexes fight soft deletes. If posts has unique(slug) and you soft-delete a post, recreating it collides. Standard fix: include deleted_at in the index or null it out on delete — decide this before you cascade anything.
  • Maintenance risk is the headline. Last release April 2021, 24 stars, 0 dependents. There is no community backstop. Check PHP 8.x deprecations and Laravel 9/10/11 compatibility against the trait source, and be prepared to vendor-fork it — it is small enough that copying the trait into app/Support/ is a legitimate strategy and removes an unsupported dependency.
  • Compare alternatives before committing. Laravel's first-party ->cascadeOnDelete() / ->nullOnDelete() migration helpers cover hard-delete FK cascades with no PHP code, and michaeldyrynda/laravel-cascade-soft-deletes is the actively maintained equivalent for soft deletes. This package's only edge is soft-delete cascading; if you do not need that, skip it.
  • Debugging technique. When a cascade silently does not happen: (a) confirm the model is instantiated (Model::booted() observers, DB::listen), (b) confirm the child uses SoftDeletes and has a deleted_at column, (c) confirm the relation name resolves — dd($model->posts()) should return a Relation, not a Collection, (d) log inside deleting/deleted to see whether events fire at all.
  • Extension point. The trait's cascade routine is the customization seam. If you need per-relation logic (e.g. skip children created before a date), override the cascade method in the model rather than adding conditionals to observers — it keeps the invariant in one place.
  • No config to tweak. Behaviour is entirely driven by the protected properties on each model, so a global change means touching every model. Document the convention in your team's model conventions doc, otherwise new models silently opt out of cascading.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings