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

Gedmo Twig Softdeleteable Bundle Laravel Package

korotovsky/gedmo-twig-softdeleteable-bundle

Symfony bundle that brings Gedmo SoftDeleteable into Twig, adding template helpers to detect and filter soft-deleted Doctrine entities. Keeps views clean by handling deleted records directly in templates without extra controller logic.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. A small Symfony bundle that registers a Twig block tag for temporarily disabling Gedmo's softdeleteable Doctrine filter while a template renders. That is the whole feature: it flips $em->getFilters()->disable('softdeleteable') on entry to a template block and restores it on exit. It is a convenience wrapper, not a soft-delete implementation — Gedmo and (usually) stof/doctrine-extensions-bundle still do the real work.

Minimal setup

  1. Install the bundle:
    composer require korotovsky/gedmo-twig-softdeleteable-bundle
    
  2. Register it. Symfony 3/4: add the bundle class from the README to AppKernel::registerBundles(). Symfony 4+/Flex:
    // config/bundles.php
    return [
        // ...
        Korotovsky\GedmoTwigSoftdeleteableBundle\GedmoTwigSoftdeleteableBundle::class => ['all' => true],
    ];
    
    The bundle namespace/class name is the thing to copy verbatim from the README — this package predates Flex and its registration instructions are easy to get wrong.
  3. Confirm the Doctrine side is already working: softdeleteable must be enabled on the entity manager, normally via Stof:
    # config/packages/stof_doctrine_extensions.yaml
    stof_doctrine_extensions:
        orm:
            default:
                softdeleteable: true
    
  4. Find out what the bundle actually registered before writing templates:
    bin/console debug:twig
    
    Look for the custom tag (and any function/filter) in the "Tags" section. The package is small enough that reading its Twig extension and token-parser classes is a five-minute job; do that rather than trusting examples.

First use case: an admin "trash" panel. Controller code normally fetches entities before Twig runs, so a template tag only helps when the query executes during rendering — an embedded controller, lazy association traversal, or a Twig function that queries. The canonical pattern is wrapping an embedded controller:

{# templates/admin/post/index.html.twig #}
{% disable_softdeleteable %}
    {{ render(controller('App\\Controller\\Admin\\TrashController::panel')) }}
{% enddisable_softdeleteable %}

While the block renders, the subrequest shares the same EntityManager, so its repository query sees the soft-deleted rows. The tag name above is the expected shape; verify the exact spelling (disable_softdeleteable vs. another form, and the matching end... tag) in debug:twig before committing it to many templates.

Implementation Patterns

Prefer the controller over the template. For anything that mutates data or is security-sensitive, disable the filter explicitly with a guaranteed restore:

$filters = $em->getFilters();
$wasEnabled = $filters->isEnabled('softdeleteable');
$wasEnabled && $filters->disable('softdeleteable');

try {
    $post = $postRepository->find($id);   // includes trashed rows
    // inspect, restore, or render
} finally {
    $wasEnabled && $filters->enable('softdeleteable');
}

Wrap this in a small service (SoftDeleteableScope::withoutFilter(callable $fn)) and use it from controllers, commands, and tests. The Twig tag then becomes a thin adapter over the same service, so behaviour is consistent everywhere.

Use the Twig tag for read-only composition. It fits when a page composes fragments and only one fragment needs trashed rows:

{# Dashboard: active posts + a small "recently deleted" audit strip #}
{% disable_softdeleteable %}
    {{ render(controller('App\\Controller\\Admin\\AuditController::recentlyDeleted', {class: 'Post'})) }}
{% enddisable_softdeleteable %}

Keep the scope as narrow as possible: one render() call, not the whole page.

Lazy-loading pattern. If an association points at soft-deleted children and you need to show them (for example, a deleted category's audit history), the traversal must happen inside the disabled scope. If the entity is already loaded and the collection already initialised, the tag does nothing — the proxy/collection won't re-query.

Restore workflow. A "restore" action is a two-phase operation: read the entity with the filter off, then clear deletedAt. Don't attempt it purely from Twig. Do it in a controller/service, and re-enable the filter before flush() so unrelated pending changes don't accidentally include entities that were only meant to be inspected.

Integration with admin generators. EasyAdmin, SonataAdmin, and Sylius all build list queries before rendering, so the template tag cannot help there. Add a custom action/controller that runs its query inside the disabled scope, then hands plain arrays/DTOs to the template. The tag is only useful for the fragments those tools render via subrequests.

Read-only reporting. For audit/export screens that must show deleted rows without allowing edits, disable the filter around the query, map results into read-only DTOs, re-enable, then render. This avoids leaving managed deleted entities in the UnitOfWork while a form or serializer runs.

Gotchas and Tips

  • The filter is global per EntityManager. Disabling affects every query in the same request from that point on: form binding, lazy loading, security voters, serializers. The Twig tag must restore the previous state on block exit (including exceptions); verify that it does, and do not nest it until you have confirmed nested save/restore semantics. If in doubt, use the controller-level helper with try/finally.
  • Identity map staleness cuts both ways. An entity already loaded (or already known to be absent) won't change when you toggle the filter. Conversely, a deleted entity loaded while the filter is off stays managed in the EntityManager after you re-enable it; a later flush() or serializer can surface it unexpectedly. Call $em->clear() or detach when crossing scope boundaries.
  • Disabling does not un-delete. deletedAt stays populated. Gedmo's listener still treats the entity as soft-deleted for lifecycle purposes; only the SQL filter is bypassed. Set deletedAt to null and flush to actually restore.
  • disable() throws if the filter is not enabled. Always guard with $em->getFilters()->isEnabled('softdeleteable'). This is a common failure in tests and in apps where Stof is configured per-entity-manager.
  • Multiple entity managers. The filter is per-EM. A tag that toggles "the" filter may only touch the default manager; multi-EM apps need to toggle each one explicitly.
  • Queries must execute inside the block. Passing a pre-fetched collection into the template and wrapping its rendering in the tag does nothing. This is the single most common misuse.
  • Deferred work leaks. Anything scheduled after the block (kernel terminate listeners, queued messages, post-render serialization) runs with whatever filter state was left behind. Make the restore unconditional.
  • Cache layers. Second-level cache and Doctrine result/query caches can mask or retain filter behaviour. When results look wrong, disable caches locally and re-test before blaming the bundle.
  • Security. Soft-deleted rows often contain data users should not see (deleted accounts, retracted content). Disabling the filter is not an authorization decision — put a voter or role check in the embedded controller, not in the template.
  • Performance. Trash/audit queries scan rows the normal filter excludes. Ensure deleted_at is indexed and paginate; a filter-disabled list view is exactly where an accidental full table scan appears.
  • Testing. In functional tests, reset the EntityManager and filters in tearDown(); a leaked disabled filter turns unrelated tests green for the wrong reason. Assert both directions: visible in the trash view, invisible in the normal view.
  • Maintenance risk is the headline. Zero stars/dependents, and a Symfony 2/3-era package against a fast-moving Gedmo/Doctrine/Symfony stack. Check compatibility with your Symfony and gedmo/doctrine-extensions versions early; if it doesn't fit, the functionality is roughly a Twig extension plus a token parser and node (tens of lines) — vendor it into your app or expose a softdeleteable_scope() function over your own service. That is often the lower-risk path.
  • Extension points. The cleanest long-term approach is your own Twig function (e.g. softdeleteable_scope(false)) backed by a service that saves/restores filter state, with the bundled tag kept only if it works on your versions. You can also decorate the bundle's extension service if you want to keep the tag and fix restore/nesting behaviour.
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.
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
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo