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

Tree Bundle Laravel Package

redcode/tree-bundle

RedCode TreeBundle adds hierarchical tree support to Symfony and Doctrine entities, making it easy to model and manage nested data such as categories, menus, and pages. It provides tree-aware entity handling, ordering, and integration with the Symfony framework.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and enable. The bundle is a thin integration layer over Sonata Admin, jsTree and Gedmo's Nested Set, so it is only useful once those three are already working.

composer require redcode/tree-bundle

Pin the version deliberately — the bundle predates Sonata Admin 4 and PHP 8 attributes, so a Composer update can pull in a Sonata major that breaks its controller/template hooks. Check composer.json constraints in the repo before composer update.

Register the bundle after SonataAdminBundle and the Doctrine extensions bundle:

// config/bundles.php
Redcode\TreeBundle\RedcodeTreeBundle::class => ['all' => true],

Import the routing (the jsTree widget calls its own AJAX endpoints, so this is not optional):

# config/routes.yaml
redcode_tree:
    resource: "@RedcodeTreeBundle/Resources/config/routing.xml"

Where to look first. Four files tell you 90% of the contract:

  • Admin/AbstractTreeAdmin.php — the base admin you extend; its protected hooks control the node label, icon and which fields appear in the tree.
  • Controller/TreeController.php — the tree, move, rename, create, delete actions the JS calls.
  • Resources/views/Tree/*.html.twig — the template Sonata renders instead of list.html.twig; override these blocks for custom node markup.
  • Resources/public/js/* — the jsTree config; this is where drag-and-drop, lazy loading and the endpoint URLs live.

First use case. A Category entity with Gedmo Nested Set, then swap its admin's list view for the tree. That is the canonical use: taxonomy, menu and page hierarchies that are edited by humans rather than by code.

/**
 * @Gedmo\Tree(type="nested")
 * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
 */
class Category
{
    /** @Gedmo\TreeLeft  @ORM\Column(type="integer") */ private $lft;
    /** @Gedmo\TreeRight @ORM\Column(type="integer") */ private $rgt;
    /** @Gedmo\TreeLevel @ORM\Column(type="integer") */ private $level;
    /** @Gedmo\TreeRoot  @ORM\Column(type="integer", nullable=true) */ private $root;

    /** @Gedmo\TreeParent @ORM\ManyToOne(targetEntity="Category") */
    private $parent;
}

Then:

class CategoryAdmin extends AbstractTreeAdmin
{
    protected $baseRouteName = 'admin_category';
    protected $baseRoutePattern = 'category';
}

and give it the tree as its model_manager/list handler per the bundle's docs, then open /admin/category/tree. If the tree renders flat, run gedmo:tree-recover on the entity — it means the lft/rgt columns are not a valid nested set yet.

Implementation Patterns

Tree-as-list, not tree-as-datagrid. The tree replaces the Sonata list view for that admin, so proxy/scope/filter configuration is bypassed. The working pattern is to keep two admins for the same entity: one AbstractTreeAdmin for structure editing, one plain AbstractAdmin for flat search, filters, batch actions and exports. Route users by a dashboard tile rather than trying to force filters into the tree.

Custom node rendering. Don't edit the bundle's Twig — subclass the admin and point getListTemplate()/the tree template hook at your own template that extends the bundle's and overrides only the label/icon blocks. This survives Composer updates; editing vendor files does not.

Permission-driven editing. jsTree drag-and-drop is gated on Sonata's isGranted() checks (EDIT for move/rename, CREATE for add-child, DELETE for delete). If nodes render but won't drag, the cause is almost always an Admin::isGranted() or voter denial rather than a JS bug. Test by logging in as ROLE_SUPER_ADMIN first.

Programmatic moves should go through Gedmo, not SQL. The tree's source of truth is lft/rgt. For imports, seed data or queue workers:

$repo = $em->getRepository(Category::class);
$repo->persistAsLastChildOf($child, $parent);   // or persistAsFirstChildOf / moveUp / moveDown
$em->flush();

If you have a parent relation, keep it in sync yourself in the same transaction — Gedmo maintains the nested set columns, and @Gedmo\TreeParent does not reliably keep the association consistent through every move path.

Reuse the tree elsewhere. The controller's JSON endpoint is a legitimate API for building a menu cache warmer, a public navigation partial, or a headless consumer. Fetch once, cache the serialized tree, and invalidate on the admin's post-persist/post-update/post-delete events.

Form integration. For the parent field on the edit form, use an EntityType ordered by lft, or Gedmo's tree form type if available in your version. Never let a user pick a descendant as a parent — add a getValidParents() query builder filter, because the bundle does not prevent that cycle for you.

Lazy loading for big trees. Above a few hundred nodes, set jsTree to lazy-load children per node rather than shipping the entire subtree. The bundle's default config sends the whole tree.

Gotchas and Tips

  • Version incompatibility is the main risk. Thirty stars, zero dependents, and a Symfony 3/4-era codebase mean you should assume no Sonata Admin 4 support. Lock sonata-project/admin-bundle and gedmo/doctrine-extensions versions in composer.json and treat any upgrade as a manual port.
  • Asset pipeline. The bundle ships Resources/public; under assets:install --symlink it works, but with Webpack Encore you must add the jsTree JS/CSS to webpack.config.js manually or the tree renders as an empty <ul>.
  • AJAX endpoints fail silently. Sonata's security layer can 302 the jsTree XHRs to the login page, which jsTree reports as "no data". Check bin/console debug:router | grep redcode and look at the Network tab for redirects, not just 500s.
  • Nested set write cost is O(n). Every insert/delete/move rewrites lft/rgt for a large part of the table. For a 50k-node category tree this makes drag-and-drop unusable; use closure or materialized-path instead, or restrict the tree admin to a small hierarchy.
  • Raw SQL inserts corrupt the tree. Any legacy import, fixture, or migration that touches these rows without the Gedmo listener must be followed by gedmo:tree-recover, and the lft/rgt columns must be indexed.
  • Multiple roots. With @Gedmo\TreeRoot mapped, the bundle draws a synthetic root node. If you see duplicate synthetic roots or nodes appearing at the top level twice, root is likely NULL for some rows — make the column nullable and re-run recovery.
  • CSRF and session. The move/rename/delete endpoints are session-authenticated; cron jobs and API tokens cannot drive them. Write your own controller that calls the repository move methods if you need programmatic structural changes.
  • Extension point. TreeController and its model manager are services — override the service definition in your app config (or decorate it) rather than extending the class, so the bundle keeps its routing wiring. That is where you add auditing: log the before/after parent on each move.
  • Debugging tip. When a move appears to succeed but the tree snaps back, the transaction rolled back on flush. Check doctrine logs for a failing UPDATE on a NOT NULL level/lft column — those must never be nullable.
  • Don't expect translations or i18n out of the box. Node labels come from your admin's field configuration; the toolbar strings come from the bundle's translation domain, which you may need to override in translations/.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
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
cgv/localized-resource