php树形菜单的实现
数据库设计
树形菜单通常使用邻接表模型存储数据。创建一个包含id、name、parent_id等字段的表,parent_id表示父节点的id,根节点的parent_id为0或NULL。
CREATE TABLE menu (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
parent_id INT DEFAULT NULL,
FOREIGN KEY (parent_id) REFERENCES menu(id) ON DELETE CASCADE
);
递归查询实现
使用递归函数从数据库获取所有菜单项并构建树形结构。这种方法适合数据量不大的情况。
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
// 使用示例
$items = $pdo->query("SELECT * FROM menu")->fetchAll(PDO::FETCH_ASSOC);
$tree = buildTree($items);
嵌套集模型实现
对于大型树结构,嵌套集模型(Nested Set)查询效率更高。需要在表中添加left和right字段。
ALTER TABLE menu ADD COLUMN lft INT NOT NULL;
ALTER TABLE menu ADD COLUMN rgt INT NOT NULL;
使用递归方法更新左右值:
function rebuildTree($parentId = 0, $left = 1) {
$right = $left + 1;
$stmt = $pdo->prepare("SELECT id FROM menu WHERE parent_id = ?");
$stmt->execute([$parentId]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$right = rebuildTree($row['id'], $right);
}
$pdo->prepare("UPDATE menu SET lft = ?, rgt = ? WHERE id = ?")
->execute([$left, $right, $parentId]);
return $right + 1;
}
前端渲染
将生成的树形数组转换为HTML菜单。使用递归函数生成嵌套的UL/LI结构。
function renderMenu($tree) {
echo '<ul>';
foreach ($tree as $node) {
echo '<li>' . htmlspecialchars($node['name']);
if (!empty($node['children'])) {
renderMenu($node['children']);
}
echo '</li>';
}
echo '</ul>';
}
使用闭包表
另一种高效方案是闭包表(Closure Table),需要额外创建关系表存储所有节点路径。
CREATE TABLE menu_closure (
ancestor INT NOT NULL,
descendant INT NOT NULL,
depth INT NOT NULL,
PRIMARY KEY (ancestor, descendant),
FOREIGN KEY (ancestor) REFERENCES menu(id),
FOREIGN KEY (descendant) REFERENCES menu(id)
);
查询子树:
$stmt = $pdo->prepare("
SELECT m.* FROM menu m
JOIN menu_closure c ON m.id = c.descendant
WHERE c.ancestor = ? AND c.depth > 0
ORDER BY c.depth
");
$stmt->execute([$parentId]);
$children = $stmt->fetchAll();
性能优化
对于大型菜单,考虑以下优化:
- 使用缓存存储生成的菜单树
- 实现延迟加载,只在需要时展开子树
- 对前端使用AJAX异步加载子树数据
// 缓存示例
$cacheKey = 'menu_tree';
if (!$tree = $cache->get($cacheKey)) {
$tree = buildTree($items);
$cache->set($cacheKey, $tree, 3600);
}






