nicmart/tree
A flexible PHP tree data structure with a fluent builder API. Create nodes with any value, add or remove children, manage parents automatically, and build hierarchies easily for menus, categories, and nested data.
What the package actually is. nicmart/tree is a dependency-free, in-memory tree data structure for PHP: a Node (value + children + auto-maintained parent pointer), a NodeInterface, a fluent NodeBuilder, and three visitors (PreOrderVisitor, PostOrderVisitor, YieldVisitor). It has no persistence, no Eloquent coupling, no query layer, no caching, and no Laravel service provider. It is a pure algorithmic helper — a better array for hierarchical data.
Architecture fit. Good fit only for the transient computation slice of a Laravel app:
with('children') or a recursive CTE, then walking them with a visitor.Poor fit as an architectural backbone:
nicmart/tree as the system of record for hierarchy would be a category error.Integration feasibility. Very high mechanically: composer require nicmart/tree, no config, no provider, no migrations, no env vars, ~zero boot cost. The real work is the adapter: mapping Eloquent models → Node values (value is mixed, so a model, an array, or a DTO all work) and mapping back. Budget 1–3 days for a clean, tested adapter plus rendering, not because the library is hard but because the boundary between "DB hierarchy" and "in-memory tree" needs an explicit owner.
Technical risk.
0.x. Minor bumps have shipped breaking changes before (return-type declarations in 0.6/0.7, dropped PHP versions in 0.3.1/0.5.0). Pin tightly (^0.10 at most, exact pin preferred) and treat upgrades as planned work.getSize()/getDepth()/getHeight() are recursive by nature. Deep or user-controlled hierarchies (imported taxonomies, maliciously deep payloads) can blow the stack. Cap depth at the boundary.addChild/setChildren, and a cycle turns any traversal into an infinite loop. Validate before construction.setChildren and shared node instances across two trees can produce surprising ancestry.Dependents: 0 and a composite score of ~28 with opportunity ~50 indicate a small, stable, low-momentum utility — not a widely battle-tested framework component. That is acceptable for a 500-line data structure and unacceptable for anything load-bearing without our own tests around it.Key questions to answer before adoption.
Stack fit. Neutral-to-positive. The library requires a modern PHP (8.1–8.4 supported per the changelog) and imposes nothing on Laravel 10/11/12. It composes cleanly with Blade (build tree → visitor → recursive partial), with API Resources (flatten via PreOrderVisitor into a list with depth/parent IDs), and with queue jobs. It does not compose with Eloquent querying — keep all filtering, scoping, and eager loading in Eloquent, then hand a pre-loaded result set to the tree builder.
Recommended boundary. Put it behind one small application service, e.g. App\Support\Tree\TreeAssembler, with:
fromModels(Collection $models): NodeInterface — adjacency list → NodeBuilder.toFlat(NodeInterface $root): array — PreOrderVisitor → rows with depth, path, parent_id.toBlade / toApiResource converters so controllers never touch Tree\* types directly.That single seam makes the library swappable (for kalnoy/nestedset, staudenmeir/laravel-adjacency-list, or a hand-rolled recursion) if depth limits or requirements change. Do not let Tree\Node leak into models, resources, or Blade data contracts.
Migration path.
children() relations, ad-hoc while ($node->parent) loops.PreOrderVisitor output. Behavior is easy to assert against fixtures; this is the lowest-risk win.Compatibility. No PHP extensions, no native deps, no conflicting packages — Composer resolution risk is essentially nil. Verify the installed tag supports the PHP version pinned in composer.json and CI matrix; the 0.5.0 release dropped PHP ≤7.4, so legacy services must not share the lockfile.
Sequencing (suggested 3 sprints of partial effort).
TreeFactory for large synthetic trees in tests to establish the depth/size ceiling empirically.Maintenance. Low, and mostly passive. No transitive dependencies means no supply-chain churn and no CVE surface beyond PHP itself. The upgrade pattern is predictable: each release adds a PHP minor and drops EOL versions, so the library effectively inherits our platform upgrade calendar. The cost is a tight version pin and a scheduled review each PHP cycle. The unglamorous risk is abandonment — a single-maintainer utility with modest stars. Mitigation is cheap: the API is ~10 methods, so a vendored fork or 200-line reimplementation is a credible exit if the repo goes quiet.
Support. Community-only, no SLA, no vendor. Internal ownership must be explicit — assign the adapter and its tests to a named team. Because the surface is small, internal expertise is a matter of hours, so bus factor is manageable if the adapter seam exists.
Scaling. Scales with request-scoped workload, not data volume. Traversals are O(n) with real object overhead (~hundreds of bytes per node); a 10k-node tree is fine on a single request, a 100k-node tree is not something to do per page view. Because there is no query capability, every operation requires the full tree in memory — this makes the package systematically wrong for large hierarchies and systematically right for small ones. Cache the flattened output, not the node graph.
Failure modes to watch.
getDepth() — enforce a max depth at assembly.setChildren/addChild with a node already in the graph — validate acyclicity in the assembler.NodeInterface in the container as a singleton or in a static property.LogicException from an empty NodeBuilder — guard getNode() when the source collection is empty; this was tightened in 0.7.2 and is an easy 500 in prod.Ramp-up. Hours for a mid-level Laravel developer: the README covers the full API, the builder DSL is self-explanatory, and there is nothing to configure. The learning curve is not the library — it is the discipline of keeping the in-memory tree out of the persistence layer and out of long-lived memory. Document that boundary in the PR template or an ADR, or the first junior dev will happily build a CategoryTree singleton and we will find it in a memory graph six months later.
How can I help you explore Laravel packages today?