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

fish/eloquent-cascade

A Laravel package for adding configurable cascade behavior to Eloquent models, helping automatically delete or update related records when a parent model changes. Define cascades in PHP to keep data consistent alongside database foreign keys.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer:

composer require fish/eloquent-cascade

Laravel’s package auto-discovery will register the service provider (if one is shipped) — for a trait-only package there’s nothing to publish and no config to edit. Open vendor/fish/eloquent-cascade/src/ first: the entire package is essentially one trait. Confirm the trait’s fully-qualified namespace there before writing code, then check the README for the exact property name it reads.

The first use case is a parent with dependent children you want removed in the same request:

use Matfish\Cascade\CascadeDeletes; // confirm namespace from src/

class Post extends Model
{
    use CascadeDeletes;

    protected $cascade = ['comments']; // relation method names
}

With that in place, $post->delete() also deletes the related comments, without you writing a deleting observer by hand. Verify with a feature test that asserts both rows are gone — this package is small enough that you should treat a passing test as your contract, not the docs.

Implementation Patterns

Declare relations, not tables. List Eloquent relation method names (comments, attachments, tags) rather than table names. The trait will resolve each relation and delete the loaded/queried models, which means relation scoping and global scopes still apply.

Nested cascades. For a deep tree (Post → Comment → Reaction), put the trait on each model and list its own children. The deletion propagates naturally as each child’s delete() fires its own cascade. This is usually clearer than listing grandchildren on the root.

class Comment extends Model
{
    use CascadeDeletes;
    protected $cascade = ['reactions'];
}

Pivot relations. For belongsToMany, decide explicitly whether you want detach() semantics or nothing at all. Cascading a many-to-many usually just means clearing the pivot row — the related model (e.g. Tag) should survive. If the trait doesn’t special-case pivots for you, override the deletion behavior or handle it in an observer.

Wrap multi-model writes in a transaction. Cascading deletes touch several tables; a half-completed cascade is a data-integrity bug. Do it at the call site:

DB::transaction(fn () => $post->delete());

Prefer explicit overrides for complex branches. If one relation needs conditional cleanup (e.g. only delete draft comments), override the cascade hook in the model or move that logic into an observer, and keep the trait list to the simple unconditional cases.

Integration with observers/policies. Order matters if you already have a deleting observer that blocks deletion. Let authorization run first (policy delete() check in the controller), then call $model->delete() and let the trait handle the rest. Don’t duplicate the cascade by also adding deleting listeners — you’ll double-delete or hit errors on already-missing rows.

Testing pattern. For each cascading model, assert the parent is gone, children are gone, and unrelated rows survive. Use RefreshDatabase and factories; this catches misnamed relation methods immediately.

Gotchas and Tips

  • Bulk deletes bypass the trait. Post::where(...)->delete() (query builder) never instantiates models, so no model events and no cascade fire. Use ->get()->each->delete(), chunkById, or move the logic to a DB-level ON DELETE CASCADE if you need bulk performance.
  • Soft deletes are a different contract. If children use SoftDeletes, delete() only sets deleted_at; it does not remove rows. Decide whether that’s intended. For hard cleanup, call forceDelete() deliberately and document it.
  • FK constraints can still fail. If your database has RESTRICT/NO ACTION foreign keys, the order of deletes matters and child-before-parent is required. The trait generally handles this, but a custom relation or pivot can break ordering. Test against a real MySQL/Postgres schema, not just SQLite.
  • Circular relations cause recursion. Two models cascading to each other will loop or throw. Break the cycle by cascading only one direction, or guard with withoutEvents() / a static flag.
  • Watch the queries. Cascading a large hasMany can silently load and delete thousands of models one at a time. Check DB::listen or Telescope for the actual query count; for big sets, prefer DB-level cascades or chunked deletes.
  • Debugging. Put a Log::info() inside the model’s deleting event, or dd($model->{$relation}()->get()) in a tinker session, to confirm the relation method resolves to what you expect. Misnamed relation methods are the #1 cause of “cascade did nothing.”
  • Extension point. Since the package is tiny (3 stars, no dependents), the trait is short enough to copy into app/Concerns/ and adapt. That removes an external dependency and lets you add pivot handling and recursion guards directly — a reasonable call for something this small and low-traffic.
  • Version pin. With no downstream users, treat upgrades as breaking by default. Pin the version in composer.json and re-run your cascade tests after any bump.
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.
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
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin