| id | parent_id | name |
|---|---|---|
| 1 | null | Acme Adventure |
| 2 | 1 | Outdoors |
| 3 | 2 | Rock Climbing |
| 4 | 3 | Helmets |
| 5 | 1 | Equipment |
| 6 | 5 | Safety |
Acme Adventure
Outdoors
Rock Climbing
Helmets
Equipment
Safety
1
2
3
4
5
6
7
8
9
10
11
12
| id | left | right | name |
|---|---|---|---|
| 1 | 1 | 12 | Acme Adventure |
| 2 | 2 | 7 | Outdoors |
| 3 | 3 | 6 | Rock Climbing |
| 4 | 4 | 5 | Helmets |
| 5 | 8 | 11 | Equipment |
| 6 | 9 | 10 | Safety |
Ancestors
Select items where the numbers are outside the numbers of the given item
Descendants
Select items where the numbers are between the numbers of the given item
Add new item
Recalculate all left values for all items on the right of the new item. Recalculate all right values for all items above and to the right of the new item.
Finding the immediate parent or children
The parent of an item is the ancestor who has no descendant who is also an ancestor of that item
| id | name |
|---|---|
| 1 | Acme Adventure |
| 2 | Outdoors |
| 3 | Rock Climbing |
| ancestor | descendant |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 1 | 2 |
| 3 | 3 |
| 1 | 3 |
| 2 | 3 |
folders
paths
$conn = new PDO('mysql...');
$root = Folder::create($conn, 'Acme Adventure');
$parent = Folder::create($conn, 'Outdoors', $root->id);/**
* Create a new Folder
*
* @param PDO $conn
* @param int $parent_id
* @return Folder
*/
public static function create(PDO $conn, $name, $parent_id = null)
{
// Insert the row
$statement = $conn->prepare('
INSERT INTO folders (name, parent_id) VALUES(?, ?)');
$statement->execute([$name, $parent_id]);
// Get the id
$id = $conn->lastInsertId();
// Create the folder
$folder = new self($conn);
$folder->id = $id;
$folder->name = $name;
$folder->parent_id = $parent_id;
// Set the paths
$folder->setFolderPaths();
return $folder;
}/**
* Set the folder paths
*
* @return void
*/
private function setFolderPaths()
{
$descendant = $this->id;
$ancestor = $this->parent_id ? $this->parent_id : $descendant;
$statement = $this->conn->prepare('
INSERT INTO paths (ancestor_id, descendant_id, depth)
SELECT ancestor_id, ?, depth+1
FROM paths
WHERE descendant_id = ?
UNION ALL SELECT ?, ?, 0
');
$statement->execute([$descendant, $ancestor, $descendant, $descendant]);
}$root = Folder::create($conn, 'Root');
$parent = Folder::create($conn, 'Parent', $root->id);
$child = Folder::create($conn, 'Child', $parent->id);
$child->delete();/**
* Delete the Folder
*
* @return void
*/
public function delete()
{
$this->deleteFolderPaths();
$statement = $this->conn->prepare('DELETE FROM folders WHERE id = ?');
$statement->execute([$this->id]);
}/**
* Delete the folder paths
*
* @return void
*/
private function deleteFolderPaths()
{
$statement = $this->conn->prepare('
DELETE FROM paths
WHERE descendant_id IN (
SELECT descendant_id FROM (
SELECT descendant_id FROM paths
WHERE ancestor_id = ?
) as tmptable
)
');
$statement->execute([$this->id]);
}$root = Folder::create($conn, 'Acme Adventures');
$grandparent1 = Folder::create($conn, 'Outdoors', $root->id);
$grandparent2 = Folder::create($conn, 'Equipment', $root->id);
$parent1 = Folder::create($conn, 'Rock Climbing', $grandparent1->id);
$parent2 = Folder::create($conn, 'Safety', $grandparent2->id);
$child = Folder::create($conn, 'Helmets', $parent1->id);
$child->move($parent2->id);/**
* Move the Folder to a new parent
*
* @param string $parent_id
* @return void
*/
public function move($parent_id)
{
$statement = $this->conn->prepare('
UPDATE folders SET parent_id = ? WHERE id = ?');
$statement->execute([$parent_id, $this->id]);
$this->parent_id = $parent_id;
$this->resetFolderPaths();
}/**
* Reset the folder paths
*
* @return void
*/
private function resetFolderPaths()
{
$statement = $this->conn->prepare('
DELETE FROM paths
WHERE descendant_id IN (
SELECT d FROM (
SELECT descendant_id as d FROM paths
WHERE ancestor_id = ?
) as dct
)
AND ancestor_id IN (
SELECT a FROM (
SELECT ancestor_id AS a FROM paths
WHERE descendant_id = ?
AND ancestor_id <> ?
) as ct
)
');
$statement->execute([$this->id, $this->id, $this->id]);
$statement = $this->conn->prepare('
INSERT INTO paths (ancestor_id, descendant_id, depth)
SELECT supertbl.ancestor_id, subtbl.descendant_id, supertbl.depth+subtbl.depth+1
FROM paths as supertbl
CROSS JOIN paths as subtbl
WHERE supertbl.descendant_id = ?
AND subtbl.ancestor_id = ?
');
$statement->execute([$this->parent_id, $this->id]);
}/**
* Get the children of a Folder
*
* @return array
*/
public function children()
{
$statement = $this->conn->prepare('
SELECT * FROM folders WHERE parent_id = ?');
$statement->execute([$this->id]);
return $statement->fetchAll();
}/**
* Get the siblings of a Folder
*
* @return array
*/
public function siblings()
{
$statement = $this->conn->prepare('
SELECT * FROM folders WHERE parent_id = ? AND id != ?');
$statement->execute([$this->parent_id, $this->id]);
return $statement->fetchAll();
}/**
* Get the ancestors of a Folder
*
* @return array
*/
public function ancestors()
{
$statement = $this->conn->prepare('
SELECT folders.* FROM folders
INNER JOIN paths ON paths.ancestor_id = folders.id
WHERE paths.descendant_id = ?
AND paths.depth > 0
');
$statement->execute([$this->id]);
return $statement->fetchAll();
}/**
* Get the descendants of a Folder
*
* @return array
*/
public function descendants()
{
$statement = $this->conn->prepare('
SELECT folders.* FROM folders
INNER JOIN paths ON paths.descendant_id = folders.id
WHERE paths.ancestor_id = ?
AND paths.depth > 0
');
$statement->execute([$this->id]);
return $statement->fetchAll();
}