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 Soft Deletes Laravel Package

dyrynda/laravel-cascade-soft-deletes

Adds cascading soft deletes to Laravel Eloquent models via a simple trait. Soft-deleting a parent also soft-deletes its related children, and restoring the parent restores them, filling the gap left by Laravel's built-in cascade, which only handles hard deletes.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Single-purpose trait package (CascadesSoftDeletes, plus a restore counterpart in recent versions) that attaches to Eloquent models via a $cascadeSoftDeletes relationship array. No service provider, no config file, no migrations, no schema changes — it operates purely on model lifecycle events.
  • Fits cleanly into an existing Laravel domain layer: it is opt-in per model, so it can be adopted incrementally without cross-cutting refactoring.
  • Architectural constraint that drives the design decision: cascades fire from the deleting model event and are executed as query-builder mass updates executed in chunks on the relation, not as per-model deletes. That means child model events (observers, audit listeners, search-index sync, broadcast/queue hooks) do not fire.
  • One of the most persistent objections to this package — that a wide cascade issues one unbounded UPDATE and can exhaust memory or lock a large row set in a single statement — was addressed upstream. 4.6.1 carries fix: iterate chunk collection individually in cascading deletes, which confirms chunked iteration is present in the 4.6.x line. This is a meaningful improvement in the package's architectural profile: cascades are now memory-bounded and issued as a series of smaller statements.

Integration feasibility

  • Very high. composer require dyrynda/laravel-cascade-soft-deletes, add the trait, declare the relationship list. Estimated effort per model is minutes, not days.
  • Requires the parent and children to already use Laravel's SoftDeletes; it is a behavior change, not a feature addition, so it needs deliberate sign-off rather than an opportunistic PR.
  • No dependency on queues, cache, or external services; works identically in local, CI, and production.
  • 4.6.1 is a patch release with no public API change. The trait surface, the $cascadeSoftDeletes contract, and the cascade semantics are unchanged. It is safe to adopt at 4.6.1 as the floor, and any model already on an earlier 4.x should be bumped to 4.6.1 specifically to pick up the chunk-iteration correctness fix.

Technical risk

  • Depth limitation (unchanged): the package does not cascade more than one level deep. Grandchildren are silently left un-deleted. For any hierarchy deeper than parent→child, this is a correctness gap that must be handled with explicit model-level orchestration or a custom implementation.
  • Silent event bypass (unchanged): because children are chunk-updated rather than saved individually, observers/audit trails/third-party sync (search, analytics, CRM) will not observe the child soft deletes. Any downstream system that infers state from model events will drift.
  • No transactional wrapper (unchanged, and slightly reframed by chunking): the loop over relationships is not atomic. Chunking reduces the blast radius of a single failed statement but increases the number of statements, so an exception midway can still leave the parent soft-deleted with a partially cascaded child set; the partial state is now potentially finer-grained. Recovery remains manual.
  • The 4.6.1 fix itself is a risk signal worth noting: a chunk-iteration defect shipped in 4.6.0 and was corrected in a patch. Any team that adopted 4.6.0 should verify cascade behaviour retrospectively — a bug in iterating the chunk collection plausibly meant some chunk members were skipped or processed incorrectly, i.e. silently uncascaded children. Treat this as a prompt to re-run orphan-detection against any environment that ran 4.6.0.
  • Force-delete asymmetry (unchanged): forceDelete() bypasses the cascade by design. If database-level ON DELETE CASCADE foreign keys also exist, deletion behavior becomes ambiguous and can surface as constraint errors.
  • Maintainer/dependency signal (slightly improved): the release shows active maintenance, a working Dependabot pipeline, and prompt turnaround on a regression — better longevity evidence than a static repo. The single-maintainer profile and small star/dependent footprint remain a low-but-nonzero supply-chain risk. The surface area is small enough that forking or inlining the trait is a credible fallback.
  • The Pest ^4.4.2 bump is dev-only. It does not enter our runtime dependency graph and imposes no constraint on our application. Do not let a composer.lock diff on dev tooling trigger a false breaking-change review.

Key questions

  1. What is the maximum relationship depth in our domain, and are we prepared to own multi-level cascades ourselves?
  2. Which child models have observers, audit logging, or external index synchronization that would be skipped?
  3. Do we have database-level ON DELETE CASCADE or restrictive foreign keys that would interact with force deletes?
  4. Is restore symmetry (cascading restores) a product requirement, and does the installed version support it?
  5. What is the chunk size, and is it configurable? There is no config file, so it is likely a hardcoded constant or trait property. For our highest-fan-out tables, determine whether that default is appropriate or whether we need to override it — the fix in 4.6.1 exists precisely because chunking behaviour is not entirely trivial.
  6. Did any environment run 4.6.0? If so, run the orphan query before assuming cascade correctness there.
  7. Do any models have self-referencing or polymorphic relationships in the cascade list?

Integration Approach

Stack fit

  • Native Laravel/Eloquent, PHP 8.x, Laravel 8–11 era. No conflict with the framework's SoftDeletes, no global scopes introduced, no query interception. Composes with policy/authorization layers, API resources, and existing repository patterns.
  • The chunked cascade implementation is still query-builder based, so it bypasses model events exactly as before — the integration shape is unchanged, only the statement profile differs.
  • Complements rather than replaces database referential integrity; it is an application-level soft-delete propagation, not a substitute for foreign keys.

Migration path

  • No data migration is required to install, but a data reconciliation pass is required to adopt: existing soft-deleted parents likely have live children today. A one-off backfill script must identify orphans (parent.deleted_at IS NOT NULL AND child.deleted_at IS NULL) and decide whether to soft-delete or intentionally retain them.
  • Any feature or report that currently relies on "child rows survive parent deletion" (analytics counts, admin lookups, billing reconciliation) must be audited before the behavior flips.
  • Ship behind a model-level opt-in sequence rather than a global rollout; the trait is per-model, which is effectively a built-in feature flag.
  • Version floor: adopt 4.6.1 or later. No migration or code change accompanies the bump; it is a drop-in patch that fixes chunk iteration.

Compatibility

  • Verify the composer.json Laravel and PHP constraints against our pinned framework version before committing; the package tracks framework releases but not always the newest major immediately.
  • The Pest 4 dev-dependency bump is internal to the package's own test suite and does not constrain our PHPUnit/Pest version, our PHP version, or our Laravel version. Ignore it in compatibility review.
  • Check for conflicting deleting listeners from other packages (activity logging, model status/history packages, media libraries). Multiple listeners on the same event are ordered by registration and can produce surprising interaction.
  • Confirm trait-inclusion order on models that define their own boot()/booted() logic.

Sequencing

  1. Inventory models and relationships; map cascade depth and fan-out; flag observers and audit expectations.
  2. Decide build-vs-adopt per model — deep hierarchies may warrant a small internal trait instead.
  3. Pin to 4.6.1+; if any environment ran 4.6.0, run the orphan query against it as a one-off verification.
  4. Add the trait and $cascadeSoftDeletes arrays leaf-first, so children are already cascade-capable when parents are enabled.
  5. Write characterization tests for delete, restore, force delete, and nested delete before enabling in shared environments. Include a high-fan-out case so the chunk boundaries are actually exercised — this is the code path that regressed in 4.6.0 and was fixed in 4.6.1.
  6. Run the orphan reconciliation backfill; record the before/after counts as evidence.
  7. Roll out model-by-model, with a scheduled orphan-detection query running continuously post-launch.
  8. Document the pattern in the engineering guidelines so future models are consistent.

Operational Impact

Maintenance

  • Low code churn, MIT licensed, tiny runtime dependency tree. The cost is not maintenance of the package but maintenance of the $cascadeSoftDeletes arrays: they are declarative configuration that silently rots when relationships are renamed or added. Renaming a relationship without updating the array produces a runtime error on delete — better than silence, but only caught if deletion paths are tested.
  • Pin the version in composer.json and treat major bumps as reviewable changes. Patch bumps can be accepted routinely, but this release is a reminder that a patch can carry a behavioural fix — read patch notes for this package rather than fast-forwarding blindly.
  • No config file means no tunable surface to maintain; the trade-off is that chunk size is not ours to adjust without forking.

Support

  • No commercial support; GitHub issues only, single maintainer. 4.6.1 is modest positive evidence on responsiveness — a regression report was turned into a patch and a dev-dependency refresh landed in the same release.
  • Given the small surface area, the realistic support plan is: one engineer reads the trait during adoption so the team can debug or fork it without dependency on upstream. Record an ADR covering both the adoption decision and the fork trigger.

Scaling

  • This release materially improves the scaling story. Cascades previously ran as a single mass UPDATE; they now execute across chunked statements, so per-statement row-lock scope and memory pressure are bounded. Wide fan-out is no longer a "one giant UPDATE holds locks for the whole cascade" failure mode.
  • Cascades still execute synchronously inside the deleting request. Chunking changes the shape of the load, not the fact that total wall-clock time scales with fan-out, and it distributes lock acquisition across many statements rather than eliminating contention. Total request latency is still the sum of the chunks.
  • Consequence: latency remains an application concern. For high-fan-out parents the pattern should still be soft-delete the parent synchronously and dispatch a queued job for the children — but the justification is now elapsed time and request timeout, not memory exhaustion. The package bounds memory for us; it does not bound latency.
  • Concurrent deletes of overlapping subgraphs can still produce deadlocks under load, and many small chunked statements can actually raise deadlock exposure versus one monotonic statement. Load-test the delete paths before enabling on high-volume tables.

Failure modes

  • Partial cascade: exception mid-loop leaves parent deleted, children live. Chunking makes the partial boundary finer-grained but does not remove the failure. Detect via a scheduled orphan query; remediate with a re-run job or manual repair.
  • Silent non-cascade: grandchildren, relationships omitted from the array, or — historically — chunk members skipped by the 4.6.0 iteration defect, remain visible while the parent is hidden. This is a data-exposure risk if child records are reachable via their own endpoints. Authorization must not assume "parent gone ⇒ child unreachable."
  • Stale derived state: search indexes, caches, and audit logs miss the child deletions because no model events fire. Requires a reconciliation job, not a code fix.
  • Force-delete conflicts: hard deletes tripping foreign keys or double-deleting where DB-level cascade is also configured.
  • Restore asymmetry: restoring a parent does not necessarily restore children (or restores children that were independently deleted earlier) — a correctness edge case to specify in tests.

Ramp-up

  • Near-zero for any Laravel developer: two lines of trait usage plus an array. Realistic onboarding is under an hour.
  • The genuine ramp-up cost is organizational, not technical — establishing the convention, the orphan-detection monitor, and the "don't assume parent deletion protects children" rule in code review. Budget roughly a day of platform-engineering time for the convention plus the reconciliation tooling, independent of how many models adopt it.
  • Add one small item to the ramp-up pack: engineers should know cascades are chunked and event-silent, and that the chunk path is the least-tested part of the package upstream. A five-minute note in the ADR covers it.
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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc