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

Soft Deleteable Extension Bundle Laravel Package

stichtingsd/soft-deleteable-extension-bundle

A Symfony bundle from Stichting SD that adds soft-delete support to Doctrine entities. Records are flagged as deleted instead of being permanently removed, allowing them to be excluded from normal queries and restored later, with bundle configuration to integrate the behavior into an application.

View on GitHub
Deep Wiki
Context7

Getting Started

Start by reading the package README and then inspect composer.json, src/, DependencyInjection/, and any Resources/config/services.* files. Because the package is small and has very low adoption, confirm whether it is an API Platform operation extension, a Doctrine/Gedmo integration bundle, or both before designing around it.

  1. Require the package:
composer require stichtingsd/soft-deleteable-extension-bundle
  1. Register the bundle manually unless a Flex recipe does it for you:
// config/bundles.php
return [
    // ...
    StichtingSD\SoftDeleteableExtensionBundle\SoftDeleteableExtensionBundle::class => ['all' => true],
];

Verify the exact class name from the package source or README; the repository spelling and Composer package spelling differ.

  1. Add a nullable deletedAt field to the entity and configure the soft-delete metadata. The exact attribute/annotation depends on what the README shows, but the typical pattern is a Gedmo-compatible field:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

#[ORM\Entity]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false, hardDelete: false)]
class Article
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeImmutable $deletedAt = null;

    public function getDeletedAt(): ?\DateTimeImmutable
    {
        return $this->deletedAt;
    }

    public function setDeletedAt(?\DateTimeImmutable $deletedAt): void
    {
        $this->deletedAt = $deletedAt;
    }
}
  1. Generate and run a migration to add the nullable column:
php bin/console make:migration
php bin/console doctrine:migrations:migrate
  1. First use case: call your normal delete endpoint or service:
DELETE /api/articles/123

Expected behavior: the row remains in the database, deleted_at is populated, and normal Doctrine/API Platform reads no longer return the record. If that does not happen, inspect the bundle’s extension/service wiring and confirm the Doctrine soft-delete filter is enabled.

Implementation Patterns

Use soft delete as the default delete path, but keep hard delete explicit. Let normal DELETE operations or domain services set deletedAt. Create a separate, permission-protected hard-delete operation for admins or cleanup jobs.

final class ArchiveArticle
{
    public function __construct(private EntityManagerInterface $em) {}

    public function __invoke(Article $article): void
    {
        $article->setDeletedAt(new \DateTimeImmutable());
        $this->em->flush();
    }
}

Restore with a custom operation or service. Soft-deleted rows are still present, so restoration is usually just clearing deletedAt:

final class RestoreArticle
{
    public function __construct(private EntityManagerInterface $em) {}

    public function __invoke(Article $article): Article
    {
        $article->setDeletedAt(null);
        $this->em->flush();

        return $article;
    }
}

Expose it as a dedicated API Platform operation, for example POST /api/articles/{id}/restore, rather than allowing clients to PATCH deletedAt directly.

Use the Doctrine filter for normal reads and explicitly bypass it for admin views. The filter should hide deleted rows from day-to-day repositories and API collections. When you need trashed records, disable the filter intentionally:

$filters = $em->getFilters();
$filterName = 'soft_deleteable'; // verify exact name from package config

if ($filters->isEnabled($filterName)) {
    $filters->disable($filterName);
}

$trashed = $articleRepository->findAll();

// Re-enable or use a fresh EntityManager if the request continues.
$filters->enable($filterName);

If the exact filter name differs, inspect config/packages/doctrine.yaml and the bundle’s service configuration.

Pattern admin screens as two scopes: active and trashed. Build repository methods such as findActive(), findTrashed(), and restore(). Keep the filter bypass isolated in those methods or a dedicated repository, not scattered through controllers.

Add API Platform operations deliberately. Use the package if it already wraps DELETE correctly. Otherwise keep the soft-delete decision in your application service and let API Platform call that service. Avoid having two delete paths: one that hard-deletes and one that soft-deletes.

Index deleted_at and any common filtered columns. Soft delete adds deleted_at IS NULL predicates to many queries. Add an index on deleted_at, and consider composite indexes for common list queries.

Protect deletedAt from mass assignment and serialization. Mark it read-only in API Platform or exclude it from write denormalization. Clients should not be able to restore or delete records by PATCHing a timestamp.

Test the lifecycle, not just the delete response. Assert that the row still exists, deleted_at is set, normal reads omit it, filtered admin reads include it, restore works, and hard delete removes it.

Gotchas and Tips

  • Maturity risk is the first gotcha. This package has very few stars, no dependents, and a low assessment score. Pin the version, review the source, and keep the integration thin enough to replace with your own Doctrine/API Platform extension or Gedmo configuration if needed.
  • Verify the spelling and class name. The repository is softdeleteable-extension-bundle, while the Composer package is soft-deleteable-extension-bundle. Confirm the bundle class and namespace from src/ before registering it.
  • The Doctrine filter is request/EntityManager-scoped. Disabling it to show trashed records affects subsequent queries in the same request. Re-enable it, scope bypasses tightly, or use a separate EntityManager for admin/export work.
  • Soft-deleted rows still violate database constraints. A unique email or slug on a soft-deleted row can still block a new row. Decide whether to null unique fields on delete, use partial/composite unique indexes, or validate with the filter disabled.
  • Raw SQL and some DQL bypass filters. Reports, native queries, batch jobs, and some association loads may include deleted rows unexpectedly. Audit reporting and export paths.
  • Cascades are not automatic. Soft-deleting a parent does not necessarily soft-delete children. Handle dependent entities explicitly or via domain events; otherwise child records remain visible or create inconsistent aggregates.
  • Hard delete needs care. To truly remove a soft-deleted entity, disable the filter, load the entity, call remove(), and flush. Do not assume remove() on a filtered-out entity will work.
  • Serialization can leak deleted data. Hide deletedAt from public output or expose it read-only. Also audit association serialization: a parent may be active while a child is soft-deleted.
  • Validation may disagree with the database. Symfony’s UniqueEntity constraint may not see soft-deleted rows when the filter is enabled, but the database still enforces uniqueness. Test duplicate creation after soft delete.
  • Time zone and mutability matter. Prefer DateTimeImmutable and a consistent timezone. If the package or Gedmo config uses timeAware, verify behavior in tests.
  • Debugging starts with the SQL log. If deleted rows still appear, check whether the filter is enabled, whether the query uses native SQL, and whether the entity metadata actually has the soft-delete mapping. Run php bin/console doctrine:query:sql "SELECT id, deleted_at FROM article" to confirm the column is populated.
  • Clear cache after wiring changes. Bundle registration, Doctrine metadata, and service config changes often require php bin/console cache:clear.
  • Extension points to prefer: decorate the package service, add your own API Platform operation/controller, or use domain services around the entity. Avoid forking the bundle for small behavior changes.
  • Keep an exit plan. Because adoption is minimal, isolate soft-delete behavior behind your own service interfaces or repository methods so replacing this bundle with direct Gedmo/Doctrine configuration or a custom API Platform extension is straightforward.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect