nicmart/tree
A lightweight, flexible tree data structure for PHP. Provides NodeInterface/Node implementations with parent/child management, leaf/child checks, and easy child add/remove/set operations. Includes fluent builder classes to assemble trees quickly.
Install via Composer: composer require nicmart/tree. Start by importing the core interfaces and classes: Tree\TreeNode (now generic) and Tree\TreeBuilder. The most common first use case is modeling hierarchical data like product categories or navigation menus.
For strongly-typed node values, leverage the generic TreeNode:
use Tree\TreeNode;
// Define a typed node for ProductCategories
$root = new TreeNode<string>('Electronics');
$phones = $root->addChild(new TreeNode<string>('Phones'));
$phones->addChild(new TreeNode<string>('Smartphones'));
For flat data integration (e.g., database rows), use TreeBuilder:
use Tree\TreeBuilder;
$nodes = [
['id' => 1, 'parent_id' => null, 'name' => 'Root'],
['id' => 2, 'parent_id' => 1, 'name' => 'Child'],
];
$tree = (new TreeBuilder())->build($nodes, 'id', 'parent_id', TreeNode::class);
Generic Node Types: Use TreeNode<T> to enforce type safety for node values (e.g., TreeNode<Category>, TreeNode<string>). This prevents runtime errors when mixing incompatible data types.
class Category { public string $name; public ?int $parentId; }
$root = new TreeNode<Category>(new Category());
Custom Node Classes: Extend TreeNode<T> to include domain logic. Override clone() for deep-copy safety when moving nodes:
class CategoryNode extends TreeNode<Category> {
public function getBreadcrumbs(): array { /* ... */ }
}
Traversing with Iterators: Use TreeIterator for depth-first/breadth-first traversal, with options to skip subtrees or limit depth. Ideal for rendering menus or calculating metrics:
$iterator = new TreeIterator($root);
$iterator->setMaxDepth(2); // Limit traversal depth
Serialization/Deserialization: Convert trees to/from arrays via toArray()/fromArray() (via TreeBuilder). Specify the generic type for accurate reconstruction:
$array = $root->toArray();
$rebuilt = (new TreeBuilder())->fromArray($array, TreeNode::class);
Reparenting & Movement: Use $node->moveTo($parent) to reassign children safely. The library enforces cycle prevention automatically.
Laravel/Eloquent Integration: Store tree metadata in the database (e.g., nestedset or closure_table), but use nicmart/tree for in-memory manipulation. Hydrate nodes from query results with explicit typing:
$nodes = Category::all()->map(fn($cat) => new CategoryNode($cat));
$tree = (new TreeBuilder())->build($nodes, 'id', 'parent_id');
Generic Type Safety: Ensure all TreeNode<T> instances use the same generic type when building trees. Mixing types (e.g., TreeNode<string> and TreeNode<Category>) in the same tree will cause runtime errors.
// ❌ Avoid mixing types
$mixedTree = new TreeNode<string>('Root');
$mixedTree->addChild(new TreeNode<Category>(new Category())); // Error
Identity vs. Equality: Nodes are compared by object identity (not data). Two TreeNode<T> instances with identical values are not considered equal unless they’re the same instance. Avoid unintentional duplication when cloning or moving.
Circular References: Cycle detection is automatic, but manually editing parent/child links (e.g., $child->setParent($child)) will throw. Use official APIs like addChild()/moveTo().
Performance with Large Trees: Deep recursion (e.g., getAncestors(), depthFirstWalk()) may hit stack limits. Use iterative approaches or limit traversal depth with TreeIterator::setMaxDepth().
Extensibility Hooks: Override TreeNode<T>::canAttach() to enforce custom constraints (e.g., max children, type restrictions). Implement JsonSerializable for tailored JSON output:
class CategoryNode extends TreeNode<Category> implements JsonSerializable {
public function jsonSerialize(): array { /* ... */ }
}
Debugging: Use $node->dump() for quick visual inspection. For complex debugging, attach Tree\TreeVisitor implementations or leverage Xdebug with generic type hints.
Backward Compatibility: The generic TreeNode is now required for all new instances. Existing non-generic TreeNode usage will continue to work but is deprecated. Update to TreeNode<T> for future compatibility.
How can I help you explore Laravel packages today?