Tree: a PHP tree structure with a fluent builder and visitor traversal
A basic but flexible tree data structure for php and a fluent tree builder implementation.
At a glance
- What is it?
- Tree is a small MIT licensed PHP package with two parts, a node abstraction with automatic parent tracking, and a builder that lets you describe a tree in a fluent chain, with traversal handled by visitor objects rather than by ad hoc recursion.
- Who is it for?
- Tree is the right tool for a PHP project that needs a tree in memory and wants readable construction code, a clear node contract and traversal that is easy to extend, and it is a good fit for configuration trees, category hierarchies and menu structures. It is the wrong choice for a tree that has to be persisted with relationships, for a large dataset that would need lazy loading, or for a case where the same node legitimately belongs to two parents.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two things in one package: a node and a builder
Tree is described as a basic but flexible tree data structure for PHP together with a handful of Builder classes that enable you to build a tree in a fluent way. The package is on Packagist as nicmart/tree, MIT licensed, and written for PHP with type declarations throughout, since the repository carries a type coverage badge from shepherd.dev alongside the code coverage badge from codecov.
The design is small enough to read in one sitting, and that is a deliberate choice rather than a limitation. The Tree\Node\NodeInterface interface abstracts the concept of a tree node, and a node in this library has essentially two things: a set of children, which implement that same interface, and a value. The Tree\Node\Node class is a straight implementation of the interface.
That two property definition is the whole model. A node holds any PHP value, and it holds children that are themselves nodes. Everything else in the library is a convenience built on those two things, and the library never imposes a type on the value, which means a node can carry a string, an object, or whatever your domain needs without any wrapper.
The audience is a PHP developer who needs a tree in memory. There is no persistence layer here, no database mapping, no lazy loading and no query language, so the package is useful when the tree is built from data you already have in hand, and it is the wrong size of tool when the tree is the thing you are querying.
The node API, and the parent pointer it maintains for you
The node methods are grouped in the readme by what they answer, and the grouping shows the shape of the abstraction.
A node is created with a value, set with setValue and read with getValue, and the value can be any PHP value. Children are added with addChild, which returns the node so calls can be chained, and read back with getChildren. The whole set can be replaced with setChildren, emptied with removeAllChildren, and a single child removed with removeChild. Two predicates answer structural questions: isLeaf is true for a node with no children, and isChild is true for a node that has a parent.
The interesting part is the parent handling. The readme says that the reference to the parent node is automatically managed by the child-modifiers methods, so once a node has been added to a parent, getParent returns it, and the getAncestors method walks up the chain to return the list of ancestors, with getAncestorsAndSelf available as the related variant that includes the node itself.
$root = (new Node('root'))
->addChild($child = new Node('child'))
->addChild($grandChild = new Node('grandchild'))
;
$grandchild->getAncestors(); // Returns [$root, $child]The rest of the API is about position and size. The root method returns the root of the tree a node belongs to, the getNeighbors method returns the siblings around a node, with getNeighborsAndSelf as the related variant, and getSize, getDepth and getHeight answer the counting questions.
Automatic parent management is convenient, and it also means the parent pointer is not yours to set. A node that has been added to a parent belongs to that parent, and the library does not describe what happens if you add the same node to a second tree.
The builder, and the end() that closes each level
The Builder exists because constructing a nested structure node by node is verbose, and the fluent form turns it into something that reads like the shape of the tree.
The builder is provided by the Tree\Builder\NodeBuilder class, and the readme is explicit that you can implement your own builder by writing an implementation of the BuilderInterface, so the construction style is not baked in.
The example builds a tree with a root, two children, and a three way branch under the second child, and the code is this.
$builder = new Tree\Builder\NodeBuilder;
$builder
->value('A')
->leaf('B')
->tree('C')
->tree('D')
->leaf('G')
->leaf('H')
->end()
->leaf('E')
->leaf('F')
->end()
;The four methods are simple. The value method sets the value of the current node. The leaf method adds a new child whose value is the given one, and does not descend into it. The tree method adds a new child with the given value and sets that new node as the builder's current node, which is the difference from leaf. The end method returns the builder to the context it was in before the call to tree, so it moves one level up.
That is the whole mental model, and it is the reason the builder is worth having. A tree method opens a level, an end method closes it, and a leaf method is a node you do not need to go back into. The indentation of the example is the structure, which is exactly what you want from a structure that is otherwise a wall of constructor calls.
Traversal through visitors, not through recursion you write
Traversal is handled by visitor objects, and the pattern is the accept method on the node.
The YieldVisitor gives you what the readme calls the yield of a tree, which is the list of leaves in a pre-order traversal.
use Tree\Visitor\YieldVisitor;
$visitor = new YieldVisitor;
$yield = $node->accept($visitor);
// $yield will contain nodes B, G, H, E, FThe PreOrderVisitor and the PostOrderVisitor work the same way, and the readme gives the node sequences they produce for the same tree, which is the clearest way to see the difference. Pre-order yields A, B, C, D, G, H, E, F, visiting a node before its children. Post-order yields B, G, H, D, E, F, C, A, visiting children before their parent, which is the order you want when a node's value depends on its subtree being complete first.
The design point is that the traversal is separated from the data. The node does not know how it is walked, and the visitor does not know what a node contains beyond calling the interface methods it needs. That means adding a traversal, a filter or a serialisation pass is a new class rather than a change to the node, and it means the same node graph can be walked in different orders without rebuilding it.
It also means the tree has to be fully in memory for a traversal to work, since the visitors walk the structure directly with no lazy loading path.
Installing the package and running its test suite
The package is a normal Composer dependency, installed with a single command.
$ composer require nicmart/treeThe test suite runs with PHPUnit, invoked directly or through the Makefile, and the tooling around it is more thorough than most small PHP libraries manage.
The Makefile's default target is called it, and it chains the coding standards, static code analysis and tests targets. The coding standards target normalises composer.json with ergebnis/composer-normalize and then fixes style issues with friendsofphp/php-cs-fixer, using a .php-cs-fixer.php configuration at the root. Static analysis runs vimeo/psalm with a psalm.xml configuration and a psalm-baseline.xml, so the baseline is checked in alongside the rules. The dependency analysis target runs maglnet/composer-require-checker with a composer-require-checker.json configuration, which is how the project proves that every dependency it declares is actually used.
There is also a mutation testing target, which runs infection/infection with an infection.json configuration, and a code coverage target that runs PHPUnit with a coverage text report. The tooling is installed with phive into a .phive directory.
The continuous integration side matches. The repository has Integrate and Release workflows, and the badges show code coverage from codecov and type coverage from shepherd.dev.
For a contributor, the workflow is make it, and for a user the whole thing is the composer require line and one class.
Where Tree is the wrong tool
Four limits, and they are structural rather than bugs.
The first is that a node has exactly one parent, and the library manages that pointer for you. That is the right model for a tree and the wrong one for a graph, and it also means the same node object cannot sit under two parents. A structure like a category tree where an item belongs to several categories is not a tree, and this library will not represent it.
The second is that everything is in memory. There is no persistence, no lazy loading and no query capability, so the tree is built from data you already hold, and the getSize, getDepth and getHeight calls walk the structure. For a large dataset that has to be loaded from a database, that is the wrong shape entirely.
The third is the small surface of the node API. The readme documents adding and removing children, setting values, asking about parents, ancestors, neighbours, size, depth and height. It does not document searching by predicate, sorting children, reordering, or removing a node and its whole subtree by value, and those are the operations a category tree usually needs.
The fourth is that the package has no opinion about identity or serialisation. A node is an in-memory object with a value, and the readme does not describe how a tree is persisted, how it is rebuilt from storage, or how nodes are matched to existing records. If your tree has to survive a restart, you are writing that layer yourself, and the library is only the container it sits in.
Licence, release cadence and what the quality signals say
The licence is MIT, with the licence file named LICENSE.md at the root of the repository and the readme stating the same thing. The code can be used, modified and redistributed with the notice kept, and this is not legal advice.
The release history shows a project that ships when it has something to ship. Versions 0.10.0 and 0.10.1 were both published on 2025-11-25, minutes apart, and 0.11.0 followed on 2026-09-14. The last push to the default branch was on 2026-10-13, and the repository is not archived, so the work is current.
The tooling is the strongest signal here, and it is worth naming the pieces because each one answers a question. Psalm with a checked in baseline answers whether the types are actually checked, and the shepherd.dev type coverage badge is a measure of how much of the API carries declarations rather than relying on inference. The mutation testing with infection answers a different question, which is whether the test suite would notice a change in behaviour, and that is the check most small libraries skip. The dependency analysis with composer-require-checker answers whether the declared dependencies are all genuinely needed, and the composer normalise step keeps the published manifest in a canonical shape.
Combined with the PHPUnit suite and the code coverage reporting, that is a well instrumented project for its size. The version numbers are still below 1.0, which for a library of this shape is normal, and the CHANGELOG.md file is the place to look for what actually changed in 0.11.0.
Editorial conclusion
Tree is the right tool for a PHP project that needs a tree in memory and wants readable construction code, a clear node contract and traversal that is easy to extend, and it is a good fit for configuration trees, category hierarchies and menu structures. It is the wrong choice for a tree that has to be persisted with relationships, for a large dataset that would need lazy loading, or for a case where the same node legitimately belongs to two parents. Before adopting, check whether your use case needs the parent pointer to be authoritative, since the library maintains it automatically, and read CHANGELOG.md to see what changed in 0.11.0, which is the most recent release.
Frequently asked questions
What is the Tree PHP package?
It is a basic but flexible tree data structure for PHP together with a fluent tree builder. The NodeInterface interface abstracts a tree node as a set of children plus a value, Node is the straight implementation, and the package is installed with composer require nicmart/tree under the MIT licence.
How do you build a tree with the Tree builder?
You call value to set the current node, leaf to add a child you do not descend into, tree to add a child and make it the current node, and end to go back one level, then getNode to retrieve the result. You can also implement the BuilderInterface yourself to replace the construction style.
How do you traverse a tree in Tree?
With visitor objects passed to the node's accept method. The YieldVisitor gives the list of leaves in a pre-order traversal, the PreOrderVisitor yields a node before its children, and the PostOrderVisitor yields children before the parent, which is the order to use when a node depends on its subtree being complete.
Does the Tree package track parent nodes automatically?
Yes. The readme states that the reference to the parent node is automatically managed by the child-modifying methods, so getParent works once a node has been added, and getAncestors and getAncestorsAndSelf walk up from there. The corollary is that a node has a single parent.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/nicmart-tree)Was this useful?