lukascivil/treewalker
Lightweight PHP library to traverse and manipulate nested data (arrays, objects, JSON) interchangeably. Includes recursive walker, diffing (new/removed/edited), structure merging, and dynamic get/set helpers. Supports PHP 8.1+.
Installation:
composer require lukascivil/treewalker
Add to composer.json if not using autoloading:
"autoload": {
"psr-4": {
"App\\": "app/",
"LukasCivil\\TreeWalker\\": "vendor/lukascivil/treewalker/src/"
}
}
Run composer dump-autoload.
First Use Case: Traverse a nested array (e.g., Laravel's route collection or a category tree):
use LukasCivil\TreeWalker\TreeWalker;
$tree = [
'id' => 1,
'name' => 'Root',
'children' => [
['id' => 2, 'name' => 'Child 1'],
['id' => 3, 'name' => 'Child 2', 'children' => [...]]
]
];
$walker = new TreeWalker($tree);
$walker->walk(function ($node) {
// Process each node (e.g., log, transform, or filter)
echo $node['name'] . "\n";
});
TreeWalker class: Core class with methods like walk(), walkRecursive(), and filter().TreeNode class: Understands how nodes are structured (must have children key for recursion).Flattening Trees: Convert hierarchical data to a flat array for processing (e.g., for Eloquent models):
$flatNodes = [];
$walker->walkRecursive(function ($node) use (&$flatNodes) {
$flatNodes[] = $node;
});
Filtering Nodes: Prune or select nodes based on conditions (e.g., active categories):
$activeNodes = $walker->filter(function ($node) {
return $node['is_active'] ?? true;
});
Transforming Trees: Modify node data during traversal (e.g., add slugs or IDs):
$walker->walk(function ($node) {
$node['slug'] = Str::slug($node['name']);
});
Integration with Laravel:
hasMany/belongsTo structures.
$categories = Category::with('children')->get();
$walker = new TreeWalker($categories->toArray());
@foreach($walker->walkRecursive() as $node)
<li>{{ $node['name'] }}</li>
@endforeach
Bulk Operations: Update multiple nodes at once (e.g., incrementing a counter):
$walker->walk(function ($node) {
$node['view_count'] = ($node['view_count'] ?? 0) + 1;
});
Custom Node Classes:
Extend TreeNode to add methods or validation:
class CategoryNode extends TreeNode {
public function isPublished() {
return $this->data['published_at'] !== null;
}
}
Usage:
$walker = new TreeWalker($categories, CategoryNode::class);
Depth Tracking: Use closures to track recursion depth (e.g., for indentation):
$walker->walkRecursive(function ($node, $depth) {
echo str_repeat(' ', $depth) . $node['name'] . "\n";
});
Lazy Loading: Load children on-demand (e.g., for large trees):
$walker = new TreeWalker($rootNode);
$walker->walk(function ($node) {
if (!isset($node['children'])) {
$node['children'] = $this->loadChildren($node['id']);
}
});
Node Structure Requirements:
children key (even if empty []).TreeWalker:
$tree = array_map(function ($node) {
return array_merge($node, ['children' => []]);
}, $rawData);
Mutable References:
walk() modify nodes by reference. Unexpected side effects can occur if not handled carefully.$walker->walk(function ($node) {
$node = array_merge($node); // Create a copy
// Safe to modify
});
Performance with Large Trees:
walkRecursive() with depth limits or iterative approaches:
$walker->walkRecursive(function ($node, $depth) {
if ($depth > 10) return; // Skip deep nodes
});
Circular References:
$visited = [];
$walker->walk(function ($node) use (&$visited) {
if (in_array($node['id'], $visited)) return;
$visited[] = $node['id'];
// Process node
});
var_dump($node) in closures to inspect data shape.$path = [];
$walker->walkRecursive(function ($node) use (&$path) {
$path[] = $node['id'];
// Debug $path if needed
});
$walker->walk(function ($node) {}) on [].$tree = ['id' => 1, 'children' => []].Custom Walkers:
Extend TreeWalker for domain-specific logic:
class CategoryTreeWalker extends TreeWalker {
public function getPublished() {
return $this->filter(function ($node) {
return $node['published'] ?? false;
});
}
}
Event-Based Traversal:
Use events (e.g., Laravel's dispatch) to trigger actions during traversal:
$walker->walk(function ($node) {
event(new NodeProcessed($node));
});
Hybrid Data Structures:
Combine with other libraries (e.g., spatie/array-to-object) for OOP traversal:
use Spatie\ArrayToObject\ArrayToObject;
$walker = new TreeWalker(ArrayToObject::convert($tree));
$walker->walk(function ($node) {
$node->slug = Str::slug($node->name);
});
Serialization: Convert trees to/from JSON or other formats:
$serialized = json_encode($walker->walkRecursive());
$tree = json_decode($serialized, true);
TreeNode class via constructor:
$walker = new TreeWalker($tree, CustomNode::class);
xdebug.max_nesting_level may need adjustment for deep trees:
xdebug.max_nesting_level=500
How can I help you explore Laravel packages today?