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

Laravel Cascade Delete Laravel Package

gigerit/laravel-cascade-delete

gigerit/laravel-cascade-delete brings trait-based cascade deletes to Laravel Eloquent models, automatically removing related records when a parent is deleted. Prevent orphaned rows without fragile DB foreign keys and keep your data consistent and relationships tidy.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require gigerit/laravel-cascade-delete and publish the package config (php artisan vendor:publish --tag="cascade-delete-config"). Read the config file before wiring anything up — it controls how recursive deletes are executed and is the first place to look when behaviour surprises you.
  • Add the package trait (e.g. CascadeDeletes) to the model that owns the relationship tree, then declare the relations that should follow it down:
use GigerIT\LaravelCascadeDelete\Traits\CascadeDeletes;

class User extends Model
{
    use CascadeDeletes;

    protected $cascadeDeletes = ['posts', 'profile'];
}
  • First use case: a User whose posts each have comments, where you want a single $user->delete() to clean the whole graph without hand-writing deleting observers or relying on database-level ON DELETE CASCADE. Declare $cascadeDeletes on User for posts and on Post for comments, then delete the user and assert the children are gone.
  • Look first at: the trait source (src/Traits/...) to see the exact deletion entry points, the published config, and the package's own test suite for the soft-delete and forceDelete cases — those define the contract you're building on.
  • Pin to ^1.3.4 in composer.json. Earlier 1.3.x releases used non-public soft-delete internals; see Gotchas.

Implementation Patterns

  • Declare relations, not collections. $cascadeDeletes entries are resolved as relationship methods on the model. They must exist and return a Relation; passing an attribute name or a pre-loaded collection silently does nothing.
  • Nested cascades compose. Each child model must itself use the trait and declare its own children. Cascading is recursive only where the trait is present — a Post without the trait stops the chain at comments.
  • Soft deletes follow the parent's mode. A normal delete() on a soft-deletable parent soft-deletes the children; forceDelete() hard-deletes them. Keep the two paths explicit in your application code (an admin "purge" action should call forceDelete() deliberately, not fall out of a generic delete controller).
  • Wrap in a transaction. For multi-level trees, put the delete inside DB::transaction(...) so a failing child doesn't leave a half-deleted graph.
  • Large trees: the package walks relations one model at a time, so each model's deleting/deleted events fire. For big subtrees, consider chunking the cascade at the top level or moving the operation to a queued job.
  • Polymorphic and pivot relations: morphMany/morphToMany work as cascade targets, but belongsToMany rows need the pivot handled — verify pivot rows are removed or detached as you expect in a test rather than assuming.
  • Coexistence with FK constraints. If your schema already has onDelete('cascade') on the same relations, you get two mechanisms doing the same job — usually harmless, occasionally noisy in query logs and confusing when debugging. Pick one per relationship and be consistent.
  • Restore is not symmetric. The package cascades deletes, not restores. If you soft-delete a parent, restoring it will not bring children back; implement an explicit restore path (observer or service) that walks the same relation list.
  • Testing: use RefreshDatabase and assert with assertSoftDeleted($child) / assertDatabaseMissing($child) on both the soft and force paths. A regression test around soft-delete vs force-delete is the cheapest guard for the v1.3.4 class of bug.

Gotchas and Tips

  • v1.3.4 — soft-delete public API. The fix replaces direct manipulation of soft-delete internals with the public API (i.e. isForceDeleting() / trashed() / forceDelete() semantics). Practical impact: if you use Laravel's stock SoftDeletes you were probably fine, but if you have a custom soft-delete trait, an overridden performDeleteOnModel, a forceDeleting property hack, or a package that swaps out SoftDeletes, earlier versions could hard-delete models you expected to be trashed (or skip them entirely). Upgrade to ≥1.3.4 and re-run your delete/force-delete tests if any of those apply. If you wrote a workaround around this bug, remove it.
  • Silent no-ops. A typo in $cascadeDeletes (or a relation that doesn't exist) typically doesn't throw — it just doesn't cascade. Confirm by adding a temporary deleting listener or asserting child rows are gone.
  • deleting returning false halts the chain for that model but may leave already-deleted siblings behind. Keep observers side-effect-light and don't use them as authorization gates — use policies before calling delete().
  • Mass deletes bypass everything. Model::where(...)->delete() on the query builder skips model events and therefore the cascade. Route through model instances, or handle those relations explicitly.
  • Self-referencing / circular relations (e.g. parent/children on the same table, or A→B→A) risk infinite recursion. Guard with depth limits or exclude the back-reference from $cascadeDeletes.
  • forceDelete() is irreversible — including children. No deleted rows remain to restore from. Gate it behind a policy or explicit confirmation, and never let it be reachable from a generic resource controller destroy method.
  • Config caching in production: after changing the published config, run php artisan config:cache. A stale cached config is a frequent "it works locally" cause.
  • Debugging recipe: in the suspect model add static::deleting(fn ($m) => logger()->info(class($m).' deleting', ['force' => $m->isForceDeleting()])) — the isForceDeleting() flag tells you immediately which path the package took on this release.
  • Extension points: override the trait's cascade method in your model and use ParentTrait::method as parentMethod; to add filtering (e.g. skip archived children) without forking the package. Observers registered on child models are the cleanest place for audit logging.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager