xcentric/soft-deleteable-extension-bundle
Xcentric SoftDeleteable Extension Bundle adds soft-delete support for Doctrine entities in Symfony, marking records as deleted instead of removing them and enabling filtered queries, restore, and permanent purge workflows.
Read this first: despite the framing, this is not a Laravel package. xcentric/soft-deleteable-extension-bundle is a Symfony bundle that plugs into Gedmo Doctrine Extensions SoftDeleteable. It is a fork of E-vence/SoftDeleteableListenerExtensionBundle. If you are in a Laravel codebase, you only encounter this package if you are running Laravel Doctrine (laravel-doctrine/orm + gedmo/doctrine-extensions) rather than Eloquent. On Eloquent, this package is irrelevant — use SoftDeletes and a model deleting hook instead.
With that caveat, minimal steps for a Symfony/Doctrine app:
composer require xcentric/soft-deleteable-extension-bundleconfig/bundles.php:
Xcentric\SoftDeleteableExtensionBundle\SoftDeleteableExtensionBundle::class => ['all' => true],
gedmo/doctrine-extensions is installed and the softdeleteable filter/listener is enabled on your entity manager (typically via stof/doctrine-extensions-bundle or a manual SoftDeleteableListener registration).Gedmo\Mapping\Annotation\SoftDeleteable (or the attribute equivalent) and have a deletedAt column.Where to look first: the bundle's DependencyInjection/ and any listener extension class. The whole point of the package is a small decorator around Gedmo's SoftDeleteableListener that adds cascade behavior — so read the listener class to see exactly which association mappings it walks (typically cascade={"remove"} associations).
First use case: an Order with OrderLine children. Deleting the Order normally soft-deletes only the order row; this bundle is meant to also soft-delete the OrderLine rows so your queries and reports stay consistent — without you having to loop manually in a service.
Entity mapping drives the cascade. The listener decides what to cascade based on Doctrine association metadata. Model your relationships so the parent declares the cascade intent the listener expects (commonly cascade={"remove"} on the owning side), and make every cascaded entity @SoftDeleteable. Entities without the soft-delete mapping will be hard-deleted or skipped — verify per entity.
#[ORM\Entity]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false)]
class Order
{
#[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderLine::class, cascade: ['remove'])]
private Collection $lines;
}
Keep application code ignorant of the cascade. The win of the package is that $em->remove($order); $em->flush(); now does the right thing. Avoid writing manual "soft-delete the children then the parent" loops in services — that defeats the purpose and creates ordering bugs.
Flush boundaries. Cascading happens during the Doctrine flush lifecycle, inside the listener's onFlush. If you build large batches, soft-delete in bounded chunks per flush; otherwise the unit of work grows and the listener cascades across an ever-larger in-memory graph.
Testing pattern. Use a real database (SQLite in-memory or a transactional test DB) rather than mocking the EntityManager. Assert:
deletedAtdeletedAtdisableFilter / repository-with-filter-disabled sees the "deleted" rows, confirming soft rather than hard deleteLaravel Doctrine integration (if you must). Register the listener extension on the manager your Laravel Doctrine package builds — usually by overriding the event manager config or extending the gedmo listener setup in a service provider. There is no Laravel service provider shipped by this package; you are wiring it yourself. If that friction is high, a plain Eloquent model deleting observer that calls ->delete() on children on a SoftDeletes model achieves the same outcome with far less ceremony.
composer require into a standard Laravel app and "just works." It expects Symfony bundle registration and a Gedmo listener. Score 0 / 0 dependents / no stars also means essentially no community support — audit the fork diff against E-vence upstream before relying on it.gedmo/doctrine-extensions. Pin compatible versions. Gedmo has changed annotation-to-attribute mapping and listener APIs across majors; a stale fork can break silently on a Gedmo upgrade.cascade on the association, a missing SoftDeleteable mapping on the child, or the child being on the inverse side where mapping isn't read.deletedAt is already set generally does nothing and may skip cascading to children. If re-deleting a parent should re-touch children, that is not guaranteed by the listener.orphanRemoval and cascade={"remove"} interact with the listener's traversal differently. Test both remove on parent and orphanRemoval on collections explicitly.$em->getFilters()->disable('softdeleteable') affects queries, not the cascade logic. Debugging "child survived the cascade" versus "child was soft-deleted but still visible" are different problems — check deletedAt directly with a raw query before concluding the listener failed.$em->getClassMetadata()->getAssociationMappings()) to confirm what the listener sees, and wrap flushes in transactions so you can inspect state before commit.cascade={"remove"}, the intended extension point is to subclass/replace the listener extension and register the substitute — don't fork the bundle for a one-off rule.deleting observer on a SoftDeletes Eloquent model is fewer moving parts, first-party, and does not carry this package's maintenance and compatibility risk.How can I help you explore Laravel packages today?