Tree extension for Doctrine

v3.2.4 2022-10-13 22:23 UTC

This package is auto-updated.

Last update: 2024-03-14 01:28:53 UTC


Licence MIT Build Status contributions welcome downloads total

This extension allows you to store your data in hierarchicaly in your database using Doctrine ORM.

Note: this code is the hard fork of Atlantic18/DoctrineExtensions 2.4.x branch of the Tree extension only.




  • 3.2.3 Fixed cache item id generator

  • 3.2.2 Automatically cache entity metadata when symfony/cache is installed.

  • 3.2.1 Fixed class and file name mismatch for XML driver

  • 3.2.0 Deprecated TreeRight, TreeLeft, TreeClosure, TreeRoot, TreePath, TreeLevel annotations, use them without "Tree" prefix (e.g. Right, Left, Closure ...)

  • 3.2.0 Added native PHP attributes support

  • 3.1.0 Renamed TreeAdapterInterface to AdapterInterface

  • 3.0.0 Changed namespace to Arodax\Doctrine\Extensions\Tree, package has been renamed to arodax/doctrine-extensions-tree. Make sure you change path and the namespace in config/packages/doctrine.yaml - see installation guide bellow for the example!

  • 2.0.0 Minimum compatible version of doctrine/common package has been increased to 3.0.*

  • 1.0.3 Implementation of #2020 removed instances of hard coded parent column in queries

  • 1.0.2 Added missing repositories from the original extension

  • 1.0.1 Implementation of #2001 fixing problem causing wrong left/right order.


Install the extension with the composer

composer require arodax/doctrine-extensions-tree

Using in the Symfony project

There is no flex recipe yet, so you need to manually enable extension by adding the following into your configuration files


                is_bundle: false
                type: annotation #attribute
                dir: '%kernel.project_dir%/vendor/arodax/doctrine-extensions-tree/src/Entity'
                prefix: 'Arodax\Doctrine\Extensions\Tree\Entity'



        class: Arodax\Doctrine\Extensions\Tree\TreeSubscriber
            - { name: doctrine.event_subscriber, connection: default }
            - [ setAnnotationReader, [ '@annotation_reader' ] ]

Prepare entity for the hierarchical tree

Annotate your entity:


namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Arodax\Doctrine\Extensions\Tree\Mapping\Annotation as Tree;

 * @ORM\Entity(repositoryClass="App\Repository\CategoryRepository")
 * @Tree\Tree(type="nested")
class Category

     * @var integer
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
    private $id;

     * @Tree\Left
     * @ORM\Column(name="lft", type="integer")
    private $lft;

     * @Tree\Level
     * @ORM\Column(name="lvl", type="integer")
    private $lvl;

     * @Tree\Right()
     * @ORM\Column(name="rgt", type="integer")
    private $rgt;

     * @Tree\Root()
     * @ORM\ManyToOne(targetEntity="MenuItem")
     * @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
    private $root;

     * @Tree\ParentNode()
     * @ORM\ManyToOne(targetEntity="MenuItem", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
    private $parent;

     * @ORM\OneToMany(targetEntity="MenuItem", mappedBy="parent")
     * @ORM\OrderBy({"lft" = "ASC"})
    private $children;

Extend the repository

Extend your entity repository from Arodax\Doctrine\Extensions\Tree\Entity\Repository\NestedTreeRepository this allows you to use special methods for working with the tree:


namespace App\Repository\Core\Menu;

use App\Entity\Category;
use Arodax\Doctrine\Extensions\Tree\Entity\Repository\NestedTreeRepository;

class MenuItemRepository extends NestedTreeRepository


Saving tree

Save some categories:


$food = new Category();

$fruits = new Category();

$vegetables = new Category();

$carrots = new Category();


The result after flush will generate the food tree:

food (1-8)
    /fruits (2-3)
    /vegetables (4-7)
        /carrots (5-6)

Inserting node in different positions


$food = new Category();

$fruits = new Category();

$vegetables = new Category();

$carrots = new Category();

    ->persistAsFirstChildOf($fruits, $food)
    ->persistAsLastChildOf($vegetables, $food)
    ->persistAsNextSiblingOf($carrots, $fruits);


Using repository functions


$repo = $em->getRepository('Entity\Category');

$food = $repo->findOneByTitle('Food');
echo $repo->childCount($food);
// prints: 3
echo $repo->childCount($food, true/*direct*/);
// prints: 2
$children = $repo->children($food);
// $children contains:
// 3 nodes
$children = $repo->children($food, false, 'title');
// will sort the children by title
$carrots = $repo->findOneByTitle('Carrots');
$path = $repo->getPath($carrots);
/* $path contains:
   0 => Food
   1 => Vegetables
   2 => Carrots

// verification and recovery of tree
// can return TRUE if tree is valid, or array of errors found on tree
$em->flush(); // important: flush recovered nodes
// if tree has errors it will try to fix all tree nodes

// UNSAFE: be sure to backup before running this method when necessary, if you can use $em->remove($node);
// which would cascade to children
// single node removal
$vegies = $repo->findOneByTitle('Vegetables');
$em->clear(); // clear cached nodes
// it will remove this node from tree and reparent all children

// reordering the tree
$food = $repo->findOneByTitle('Food');
$repo->reorder($food, 'title');
// it will reorder all "Food" tree node left-right values by the title

For more examples and usage check original package documentation: