Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 104 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,115 @@ composer require yii2-extensions/nested-sets-behavior

### How it works

The nested sets model is a technique for storing hierarchical data in a relational database. Unlike adjacency lists
(parent_id approach), nested sets enable efficient tree operations with minimal database queries.

1. **Creates root nodes** using the nested sets pattern with `lft`, `rgt`, and `depth` fields.
2. **Manages hierarchy** automatically when inserting, moving, or deleting nodes.
3. **Optimizes queries** using boundary values for efficient tree traversal.
4. **Supports transactions** to ensure data integrity during complex operations.

#### Why nested sets?

- **Fast queries**: Get all descendants with a single query (`lft BETWEEN parent.lft AND parent.rgt`).
- **Efficient tree operations**: No recursive queries needed for tree traversal.
- **Automatic maintenance**: Left/right boundaries are calculated automatically.
- **Depth tracking**: Easy to limit query depth or build breadcrumbs.

```text
Example tree structure:
Electronics (1,12,0)
├── Mobile Phones (2,7,1)
│ └── Smartphones (3,6,1)
│ └── iPhone (4,5,2)
└── Computers (8,11,1)
└── Laptops (9,10,2)

Numbers represent: (left, right, depth)
```

### Database setup

The package includes ready-to-use migrations for creating the necessary database structure.

#### Quick setup (Recommended)

1. **Configure console application**:
```php
<?php

declare(strict_types=1);

use yii\console\controllers\MigrateController;

// console/config/main.php
return [
'controllerMap' => [
'migrate' => [
'class' => MigrateController::class,
'migrationPath' => [
'@console/migrations',
'@vendor/yii2-extensions/nested-sets-behavior/migrations',
],
],
],
];
```

2. **Run migrations**:
```bash
# For single tree structure
./yii migrate/up m250707_103609_tree

# For multiple trees structure
./yii migrate/up m250707_104009_multiple_tree
```

#### Alternative: Direct migration execution

```bash
# Run without configuration changes
./yii migrate/up --migrationPath=@vendor/yii2-extensions/nested-sets-behavior/migrations
```

#### Table structures created

**Single tree** (`m250707_103609_tree.php`). Creates a `tree` table for single hierarchical structure.

```sql
CREATE TABLE `tree` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`lft` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
`depth` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_tree_lft` (`lft`),
KEY `idx_tree_rgt` (`rgt`),
KEY `idx_tree_depth` (`depth`),
KEY `idx_tree_lft_rgt` (`lft`,`rgt`)
);
```

**Multiple trees** (`m250707_104009_multiple_tree.php`). Creates a `multiple_tree` table for multiple independent trees.

```sql
CREATE TABLE `multiple_tree` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tree` int(11) DEFAULT NULL, -- Tree identifier
`name` varchar(255) NOT NULL,
`lft` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
`depth` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_multiple_tree_tree` (`tree`),
KEY `idx_multiple_tree_lft` (`lft`),
KEY `idx_multiple_tree_rgt` (`rgt`),
KEY `idx_multiple_tree_depth` (`depth`),
KEY `idx_multiple_tree_tree_lft_rgt` (`tree`,`lft`,`rgt`)
);
```

### Basic Configuration

Add the behavior to your ActiveRecord model.
Expand Down Expand Up @@ -111,31 +215,6 @@ class Category extends ActiveRecord
}
```

### Database schema

Create the required database fields.

```sql
-- Single tree structure
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
depth INT NOT NULL
);

-- Multiple trees structure
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
tree INT,
name VARCHAR(255) NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
depth INT NOT NULL
);
```

### Basic Usage

#### Creating and building trees
Expand Down
7 changes: 6 additions & 1 deletion migrations/m250707_103609_tree.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ public function safeUp()
{
$rawPrimaryKey = 'NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';

if ($this->db->driverName === 'mysql') {
$tableOptions = 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';
}

$this->createTable(
'{{%tree}}',
[
'id' => $this->db->driverName !== 'oci' ? $this->primaryKey()->notNull() : $rawPrimaryKey,
'name' => $this->db->driverName === 'oci' ? $this->string()->notNull() : $this->text()->notNull(),
'name' => $this->string(255)->notNull(),
'lft' => $this->integer()->notNull(),
'rgt' => $this->integer()->notNull(),
'depth' => $this->integer()->notNull(),
],
$tableOptions ?? null,
);

$this->createIndex('idx_tree_lft', '{{%tree}}', 'lft');
Expand Down
7 changes: 6 additions & 1 deletion migrations/m250707_104009_multiple_tree.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ public function safeUp()
{
$rawPrimaryKey = 'NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';

if ($this->db->driverName === 'mysql') {
$tableOptions = 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';
}

$this->createTable(
'{{%multiple_tree}}',
[
'id' => $this->db->driverName !== 'oci' ? $this->primaryKey()->notNull() : $rawPrimaryKey,
'tree' => $this->integer()->null(),
'name' => $this->db->driverName === 'oci' ? $this->string()->notNull() : $this->text()->notNull(),
'name' => $this->string(255)->notNull(),
'lft' => $this->integer()->notNull(),
'rgt' => $this->integer()->notNull(),
'depth' => $this->integer()->notNull(),
],
$tableOptions ?? null,
);

$this->createIndex('idx_multiple_tree_tree', '{{%multiple_tree}}', 'tree');
Expand Down
Loading