Optimizing PHP Recursive Tree Building with Native Array Functions
When building hierarchical systems like permission trees or category menus, recursive algorithms are often the first choice. However, traditional recursion in PHP can become prohibitively slow when dealing with datasets exceeding 10,000 records and nesting beyond three levels.
A practical solution is to replace deep recursion with iterative filtering using native PHP array functions — specifically array_filter. This approach avoids the overhead of repeated function calls and drastically improves perofrmance.
Sample Table Structure
CREATE TABLE `admin_permission` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`parent_permission_id` int unsigned NOT NULL DEFAULT '0',
`permission_name` varchar(50) NOT NULL,
`permission_url` varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `parent_permission_id` (`parent_permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Performance Comparison
With ~14,350 records:
- Traditional Recursion: ~6 seconds per run
- Native Array Filtering: ~1 second or less
Inefficient Recursive Approach
public static function buildTreeRecursive(array $items, int $parentId = 0, string $parentKey = 'parent_id'): array {
$branch = [];
foreach ($items as $item) {
if ($item[$parentKey] == $parentId) {
$children = self::buildTreeRecursive($items, $item['id'], $parentKey);
if (!empty($children)) {
$item['children'] = $children;
}
$branch[] = $item;
}
}
return $branch;
}
Optimized Iterative Approach (3-Level Example)
$rootItems = array_filter($flatList, fn($item) => $item[$parentField] == 0);
foreach ($rootItems as &$level1) {
$level1['children'] = array_filter($flatList, fn($item) => $item[$parentField] == $level1['id']);
foreach ($level1['children'] as &$level2) {
$level2['children'] = array_filter($flatList, fn($item) => $item[$parentField] == $level2['id']);
// Optional: Add third level
// foreach ($level2['children'] as &$level3) {
// $level3['children'] = array_filter($flatList, fn($item) => $item[$parentField] == $level3['id']);
// }
}
}
Complete Benchmarking Snippet
// Fetch flat list from DB
$permissions = AdminPermission::orderBy('id')->get(['id', 'parent_permission_id', 'permission_name'])->toArray();
echo "Total records: " . count($permissions) . "\n";
// Test recursive method
$start = microtime(true);
$treeRecursive = self::buildTreeRecursive($permissions, 0, 'parent_permission_id');
echo "Recursive: " . (microtime(true) - $start) . " seconds\n";
// Test filtered method
$start = microtime(true);
$treeFiltered = self::buildTreeFiltered($permissions, 'parent_permission_id');
echo "Filtered: " . (microtime(true) - $start) . " seconds\n";
Scalability Recommendations
- For deeply nested structures, consider lazy loading — fetch children only when expanded.
- Implement pagination or virtual scrolling for large sibling lists.
- Avoid AI-generated "optimizations" that ignore real-world data access patterns.
- Cache built trees when source data is relatively static.