iatstuti/laravel-cascade-soft-deletes
Laravel Cascade Soft Deletes adds an Eloquent trait that automatically cascades soft deletes, restores, and force deletes to related models, keeping parent and child records consistent without custom model event code.
Architecture fit. The package is a single Eloquent trait (CascadesSoftDeletes) that hooks the deleting, restoring, and force-delete model events to propagate soft deletes and restores across declared relationships. It fills a genuine Laravel gap: SoftDeletes is an application-level concept, so database ON DELETE CASCADE foreign keys do nothing for it, and out of the box a soft-deleted parent leaves fully active children behind. For any product where "deleted" must mean "consistently hidden across the aggregate" — audit retention, GDPR-style erasure-then-restore windows, tenant offboarding, user content removal — this is a direct fit rather than a nice-to-have. It is idiomatic Eloquent, requires no schema changes, no service provider, and no configuration, so architectural disruption is near zero.
Integration feasibility. Adoption is per-model and incremental: composer require, use CascadesSoftDeletes, declare protected $cascadeDeletes = [...] (or call cascadeSoftDeletes()), and children that also use the trait cascade recursively. Because adoption is opt-in per model, we can pilot on a low-risk aggregate and widen without a big-bang migration. The main integration work is not the package but the discipline around it: auditing which models participate, and ensuring every deletion path goes through model instances rather than query builders.
Technical risk. Moderate and mostly operational rather than technical:
Model::where(...)->delete() and raw SQL skip model events entirely, so bulk/admin/queue deletions will silently orphan children. This is the single largest correctness risk and is a code-review and lint problem, not something the package can solve.save() calls. Unless the caller wraps the delete in DB::transaction(), a failure mid-graph leaves a partially cascaded state with no rollback.Key questions. Which models and relationships are in scope, and is that graph provably acyclic? Do all deletion paths go through Eloquent instances, or do bulk operations, jobs, and admin tooling exist? What is the realistic maximum fan-out per delete, and what is our acceptable lock/latency budget? Do we need hard-delete/purge semantics beyond soft delete, and does the package's force-delete handling cover it? What Laravel and PHP versions must we support, and is the package's supported matrix current? And critically: is an event-driven trait the right mechanism for our compliance story, or do we need an immutable audit log or DB trigger that cannot be bypassed?
Stack fit. Standard Composer package for Laravel applications on PHP 8.x; pure Eloquent with no infrastructure footprint, no queue worker, no external service. It fits a monolith or modular monolith cleanly. It fits less cleanly where other services or analysts write directly to the same tables, because the cascade is only enforced inside this application's model layer — that is a governance boundary, not a technical one.
Migration path. Start with one bounded aggregate (comments, attachments, notifications — high volume, low business risk). Add the trait and explicit $cascadeDeletes declarations, wrap the delete call sites in DB::transaction(), and ship. Run a one-time reconciliation query to find existing soft-deleted parents with active children and align them with the new semantics. Once the pilot is clean for a release cycle, extend model by model with a checklist: relation declared, inverse relation reviewed for cycles, child model uses the trait if nesting is required, tests added.
Compatibility. Pin the version explicitly and verify the support matrix against our Laravel/PHP versions in CI rather than trusting local installs. Check for trait method collisions (bootCascadesSoftDeletes versus other traits booting the same events) and for observers on deleting/restoring whose ordering relative to the cascade matters — observers that assume children are still active will behave differently after adoption. Test against SQLite for speed but validate lock and constraint behavior on the production engine (MySQL/Postgres), since restore-order constraint violations only surface there.
Sequencing. (1) Inventory models, relationships, and deletion call sites; identify bulk paths. (2) Establish the transaction wrapper convention at the service/repository layer before any cascade is enabled. (3) Pilot one aggregate with tests covering delete, restore, nested cascade, and bulk-bypass expectations. (4) Add an automated check (static analysis or a test that enumerates SoftDeletes models) requiring every soft-deleting model to declare its cascade intent, including an explicit empty declaration. (5) Roll out by domain. (6) Turn on orphan-detection monitoring before declaring done.
Maintenance. Small, focused, MIT-licensed codebase with roughly 1.2k stars and a recent release (2026-03-20), which suggests active stewardship. The offsetting signal is the reported dependent count of zero — ecosystem telemetry is effectively absent, so we cannot lean on a large population of production users surfacing edge cases. Treat it as a small dependency we should be prepared to own: the code surface is small enough that forking or inlining the trait is a realistic fallback if it goes stale. Budget a small recurring allowance for version bumps tied to Laravel majors.
Support. No commercial support; community issues only. Escalation path is "read the trait, patch it, or vendor it." That is acceptable for a ~200-line trait in a non-security-critical path, and unacceptable if deletions become part of a compliance attestation without an internal owner.
Scaling. Cost is proportional to graph size per deletion, executed synchronously in the request or job. At low fan-out this is invisible; at thousands of children per parent it becomes a latency and lock-duration problem. Mitigations: cap cascade breadth by design, move large deletions to queued jobs with chunked child processing, avoid cascading across very high-volume tables, and monitor delete duration as a first-class metric. Restores are typically heavier than deletes and should be assumed to be asynchronous for large aggregates.
Failure modes to instrument. Orphaned active children under soft-deleted parents (the canary for event bypass and partial cascades); exceptions mid-cascade leaving partial state (detect via transaction rollback metrics); recursion or timeout on cyclic graphs; unique-constraint failures during restore; and silent no-ops from misconfigured relation names. Each maps to a concrete alert: a nightly orphan-count query, delete/restore error rates, p95 delete latency, and a CI test asserting every soft-deleting model declares cascade intent.
Ramp-up. Low for individual developers — a mid-level Laravel engineer can read the trait and use it correctly in under an hour. The real ramp-up is organizational: teams must internalize "never bulk-delete a soft-deleting model," wrap deletes in transactions, and declare cascade intent explicitly. Encode that in the PR checklist and the CI guard rather than in documentation alone, and record the decision in a short ADR so the next team inherits the reasoning alongside the dependency.
TPM position. Worth adopting for a bounded, acyclic set of aggregates where consistent soft-delete semantics have real product or compliance value. Do not adopt it as a blanket policy across every model in the schema, and do not treat it as an enforcement mechanism — it is a convenience layer over Eloquent events, and the guarantees it provides are exactly as strong as the discipline of the code that calls it.
How can I help you explore Laravel packages today?