本文目录导读:

- 邻接表模型(Adjacency List)
- 嵌套集模型(Nested Set)
- JSON存储
- 内存树结构(PHP数组)
- 使用第三方库:Staudenmeir/ Laravel-adjacency-list
- 选择建议
- 最佳实践建议
在PHP中存储树形结构有多种方法,以下是几种常见的方案:
邻接表模型(Adjacency List)
最常用的方式,每个节点存储父节点的ID。
数据库表结构:
CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
parent_id INT DEFAULT NULL,
sort_order INT DEFAULT 0,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
PHP操作示例:
class CategoryTree {
private $db;
public function __construct($pdo) {
$this->db = $pdo;
}
// 添加节点
public function addNode($name, $parentId = null) {
$sql = "INSERT INTO categories (name, parent_id) VALUES (?, ?)";
$stmt = $this->db->prepare($sql);
return $stmt->execute([$name, $parentId]);
}
// 获取所有节点并构建树
public function getTree() {
$sql = "SELECT * FROM categories ORDER BY parent_id, sort_order";
$stmt = $this->db->query($sql);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 构建树结构
$tree = [];
$map = [];
foreach ($items as $item) {
$map[$item['id']] = array_merge($item, ['children' => []]);
}
foreach ($map as $id => &$node) {
if ($node['parent_id'] && isset($map[$node['parent_id']])) {
$map[$node['parent_id']]['children'][] = &$node;
} else {
$tree[] = &$node;
}
}
unset($node);
return $tree;
}
// 递归获取子树
public function getSubtree($parentId) {
$sql = "SELECT * FROM categories WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$parentId]);
$node = $stmt->fetch(PDO::FETCH_ASSOC);
if ($node) {
$node['children'] = $this->getChildren($parentId);
}
return $node;
}
private function getChildren($parentId) {
$sql = "SELECT * FROM categories WHERE parent_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$parentId]);
$children = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($children as &$child) {
$child['children'] = $this->getChildren($child['id']);
}
return $children;
}
}
嵌套集模型(Nested Set)
适合频繁查询子树和层级关系的场景。
数据库表结构:
CREATE TABLE categories_nested (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
level INT NOT NULL
);
PHP操作示例:
class NestedSet {
private $db;
public function __construct($pdo) {
$this->db = $pdo;
}
// 添加根节点
public function addRoot($name) {
$sql = "SELECT COALESCE(MAX(rgt), 0) + 1 FROM categories_nested";
$lft = $this->db->query($sql)->fetchColumn();
$sql = "INSERT INTO categories_nested (name, lft, rgt, level) VALUES (?, ?, ?, 0)";
$stmt = $this->db->prepare($sql);
return $stmt->execute([$name, $lft, $lft + 1]);
}
// 添加子节点
public function addNode($name, $parentId) {
$sql = "SELECT lft, rgt, level FROM categories_nested WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$parentId]);
$parent = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$parent) return false;
// 更新所有受影响节点的左右值
$sql = "UPDATE categories_nested SET rgt = rgt + 2 WHERE rgt > ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$parent['lft']]);
$sql = "UPDATE categories_nested SET lft = lft + 2 WHERE lft > ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$parent['lft']]);
// 插入新节点
$sql = "INSERT INTO categories_nested (name, lft, rgt, level) VALUES (?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
return $stmt->execute([$name, $parent['lft'] + 1, $parent['lft'] + 2, $parent['level'] + 1]);
}
// 获取整个树
public function getTree() {
$sql = "SELECT id, name, level FROM categories_nested ORDER BY lft";
return $this->db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
// 获取某节点的所有后代
public function getDescendants($nodeId) {
$sql = "SELECT child.*
FROM categories_nested AS child,
categories_nested AS parent
WHERE parent.id = ?
AND child.lft BETWEEN parent.lft AND parent.rgt
ORDER BY child.lft";
$stmt = $this->db->prepare($sql);
$stmt->execute([$nodeId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
JSON存储
适合数据量小,需要灵活性高的场景。
// 数据库表结构
/*
CREATE TABLE tree_data (
id INT PRIMARY KEY AUTO_INCREMENT,
data JSON NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
*/
class JsonTree {
private $db;
public function __construct($pdo) {
$this->db = $pdo;
}
public function saveTree($treeData) {
$json = json_encode($treeData, JSON_UNESCAPED_UNICODE);
$sql = "INSERT INTO tree_data (data) VALUES (?)";
$stmt = $this->db->prepare($sql);
return $stmt->execute([$json]);
}
public function getTree($id) {
$sql = "SELECT data FROM tree_data WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$id]);
$data = $stmt->fetchColumn();
return json_decode($data, true);
}
// 在内存中操作树
public function addNode(&$tree, $parentId, $node) {
foreach ($tree as &$item) {
if ($item['id'] === $parentId) {
$item['children'][] = $node;
return true;
}
if (isset($item['children'])) {
if ($this->addNode($item['children'], $parentId, $node)) {
return true;
}
}
}
return false;
}
}
内存树结构(PHP数组)
适合数据量较小,不需要持久化的场景。
class MemoryTree {
private $tree = [];
// 初始化树
public function buildTree($data, $parentId = 0) {
$branch = [];
foreach ($data as $item) {
if ($item['parent_id'] === $parentId) {
$children = $this->buildTree($data, $item['id']);
if ($children) {
$item['children'] = $children;
}
$branch[] = $item;
}
}
return $branch;
}
// 遍历树
public function traverseTree($tree, &$result = []) {
foreach ($tree as $node) {
$result[] = $node['id'];
if (isset($node['children'])) {
$this->traverseTree($node['children'], $result);
}
}
return $result;
}
// 查找节点
public function findNode($tree, $id) {
foreach ($tree as $node) {
if ($node['id'] == $id) {
return $node;
}
if (isset($node['children'])) {
$found = $this->findNode($node['children'], $id);
if ($found) {
return $found;
}
}
}
return null;
}
}
使用第三方库:Staudenmeir/ Laravel-adjacency-list
如果你使用Laravel框架,可以使用成熟的第三方包。
// composer require staudenmeir/laravel-adjacency-list
use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
class Category extends Model {
use HasRecursiveRelationships;
// 获取子树
public function getTree() {
return $this->descendants()->get();
}
// 获取整个树
public static function allTree() {
return static::tree()->get();
}
// 找祖先
public function getAncestors($id) {
return static::find($id)->ancestors()->get();
}
}
选择建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 邻接表 | 简单易懂,插入删除容易 | 查询子树需要递归 | 大多数场景 |
| 嵌套集 | 查询效率高,无需递归 | 插入删除复杂,维护成本高 | 频繁查询、较少修改 |
| JSON | 灵活,结构自由 | 查询、更新困难 | 配置类数据 |
| 内存数组 | 速度快,简单 | 不持久化 | 临时数据、测试 |
最佳实践建议
- 数据量小(<1000条):使用邻接表
- 数据量大且查询频繁:使用嵌套集
- 需要灵活结构:使用JSON
- 使用Laravel:建议使用专业库(如laravel-adjacency-list)
选择存储方案时,要综合考虑:
- 数据量大小
- 查询模式(读取多还是写入多)
- 树深度(浅树还是深树)
- 是否需要跨层查询
- 维护成本
希望这些方案能帮助你选择最适合的树形结构存储方式!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播