Hierarchy Component

Nested Set benefits with Adjacency List effort. Retrieve the children, counts, levels, paths, trees, nests, and leaves from your category nodes.

use BootPress\Hierarchy\Component as Hierarchy;

Packagist License MIT HHVM Tested PHP 7 Supported Build Status Code Climate Test Coverage


The BootPress Hierarchy Component bridges the gap between Adjacency lists and the Nested Set Model, so that you can have both the simplicity of Adjacency lists, with the power and efficiency of Nested sets.

public __construct ( object $db , string $table [, string $id = 'id' ] )

The $db $table must have the following fields:

  • $id => 'INTEGER PRIMARY KEY',
  • 'parent' => 'INTEGER NOT NULL DEFAULT 0',
  • 'level' => 'INTEGER NOT NULL DEFAULT 0',
  • 'lft' => 'INTEGER NOT NULL DEFAULT 0',
  • 'rgt' => 'INTEGER NOT NULL DEFAULT 0',

All you need to worry about is the $id and 'parent'. This class will take care of the rest.

@param $db

A BootPress\Database\Component instance.

@param $table

The name of the database's hierarchical table. Saved in the $hier->table private property.

@param $id

The database table's id column. Saved in the $hier->id private property.

@example
use BootPress\Database\Component as Database;
use BootPress\Hierarchy\Component as Hierarchy;

$db = new Database('sqlite::memory:');
$db->exec(array(
    'CREATE TABLE category (',
    '    id INTEGER PRIMARY KEY,',
    '    name TEXT NOT NULL DEFAULT "",',
    '    parent INTEGER NOT NULL DEFAULT 0,',
    '    level INTEGER NOT NULL DEFAULT 0,',
    '    lft INTEGER NOT NULL DEFAULT 0,',
    '    rgt INTEGER NOT NULL DEFAULT 0',
    ')',
));
if ($stmt = $db->insert('category', array('id', 'name', 'parent'))) {
    $db->insert($stmt, array(1, 'Electronics', 0));
    $db->insert($stmt, array(2, 'Televisions', 1));
    $db->insert($stmt, array(3, 'Tube', 2));
    $db->insert($stmt, array(4, 'LCD', 2));
    $db->insert($stmt, array(5, 'Plasma', 2));
    $db->insert($stmt, array(6, 'Portable Electronics', 1));
    $db->insert($stmt, array(7, 'MP3 Players', 6));
    $db->insert($stmt, array(8, 'Flash', 7));
    $db->insert($stmt, array(9, 'CD Players', 6));
    $db->insert($stmt, array(10, '2 Way Radios', 6));
    $db->insert($stmt, array(11, 'Apple in California', 1));
    $db->insert($stmt, array(12, 'Made in USA', 11));
    $db->insert($stmt, array(13, 'Assembled in China', 11));
    $db->insert($stmt, array(14, 'iPad', 13));
    $db->insert($stmt, array(15, 'iPhone', 13));
    $db->close($stmt);
}
$hier = new Hierarchy($db, 'category', 'id');

public refresh ( [ string $order = null ] )

Refreshes the database table's 'level', 'lft', and 'rgt' columns. This should be called any time you insert into, or delete from your hierarchical table.

@param $order

The table's field name you would like to base the order on. The default is to use the $hier->id field.

@example
$hier->refresh();

public bool|array delete ( int $id )

Delete a node and all of it's children from your hierarchical table.

@param $id

The $hier->id of the node.

@return

Either false if nothing was affected, or an array() of deleted ids.

@example
print_r($hier->delete(11)); // array(11, 12, 13, 14, 15)
$hier->refresh(); // don't forget to do this!

public bool|int id ( string $field , array $values )

Get the id of a given path.

@param $field

The $hier->table's column name.

@param $values

The $field values to drill down.

@return

Either false if no result found, or an integer id.

@example
echo $hier->id('name', array('Electronics')); // 1
echo $hier->id('name', array('Electronics', 'Portable Electronics', 'CD Players')); // 9
echo $hier->id('name', array('Electronics', 'Apple in California')); // false

public array path ( string $field , string $value [, string|array $column = null ] )

Retrieve a single path.

@param $field

The $hier->table's column name.

@param $value

Of the $field.

@param $column

The column(s) that you want to return. The default is the $field you specify.

@return

An array of $hier->id (keys) and $column (values).

@example
var_export($hier->path('name', 'Flash'));
array(
    1 => 'Electronics',
    6 => 'Portable Electronics',
    7 => 'MP3 Players',
    8 => 'Flash',
)

var_export($hier->path('id', 9, array('level', 'name', 'parent')));
array(
    1 => array('level' => 0, 'name' => 'Electronics', 'parent' => 0),
    6 => array('level' => 1, 'name' => 'Portable Electronics', 'parent' => 1),
    9 => array('level' => 2, 'name' => 'CD Players', 'parent' => 6),
)

public array children ( int $id , string|array $column )

Find the immediate subordinates of a node ie. no grand children.

@param $id

The $hier->id of the node.

@param $column

The column(s) that you want to return.

@return

An array of $hier->id (keys) and $column (values).

@example
var_export($hier->children(6, 'name'));
array(
    7 => 'MP3 Players',
    9 => 'CD Players',
    10 => '2 Way Radios',
)

var_export($hier->children(6, array('level', 'name')));
array(
    7 => array('level' => 2, 'name' => 'MP3 Players'),
    9 => array('level' => 2, 'name' => 'CD Players'),
    10 => array('level' => 2, 'name' => '2 Way Radios'),
)

public array level ( int $depth , string|array $column )

Find all the nodes at a given level.

@param $depth

The level you want, starting at 0.

@param $column

A single column string, or an array of columns that you want to return.

@return

An array of $hier->id (keys) and $column (values).

@example
var_export($hier->level(2, array('parent', 'name')));
array(
    3 => array('parent' => 2, 'name' => 'Tube'),
    4 => array('parent' => 2, 'name' => 'LCD'),
    5 => array('parent' => 2, 'name' => 'Plasma'),
    7 => array('parent' => 6, 'name' => 'MP3 Players'),
    9 => array('parent' => 6, 'name' => 'CD Players'),
    10 => array('parent' => 6, 'name' => '2 Way Radios'),
)

public int|array counts ( string $table , string $match [, int $id = null ] )

Aggregate the total records in a table for each tree node.

@param $table

The database table to aggregate the records from.

@param $match

The $table column that corresponds with the $hier->id.

@param $id

A specific node you may be looking for.

@return

The total count(s).

@example
$db->exec(array(
    'CREATE TABLE products (',
    '    id INTEGER PRIMARY KEY,',
    '    category_id INTEGER NOT NULL DEFAULT 0,',
    '    name TEXT NOT NULL DEFAULT ""',
    ')',
));

if ($stmt = $db->insert('products', array('category_id', 'name'))) {
    $db->insert($stmt, array(3, '20" TV'));
    $db->insert($stmt, array(3, '36" TV'));
    $db->insert($stmt, array(4, 'Super-LCD 42"'));
    $db->insert($stmt, array(5, 'Ultra-Plasma 62"'));
    $db->insert($stmt, array(5, 'Value Plasma 38"'));
    $db->insert($stmt, array(7, 'Power-MP3 128mb'));
    $db->insert($stmt, array(8, 'Super-Shuffle 1gb'));
    $db->insert($stmt, array(9, 'Porta CD'));
    $db->insert($stmt, array(9, 'CD To go!'));
    $db->insert($stmt, array(10, 'Family Talk 360'));
    $db->close($stmt);
}

echo $hier->counts('products', 'category_id', 2); // 5 (Televisions - category_id's 3, 4, and 5)

var_export($hier->counts('products', 'category_id'));
array( // id => count
    1 => 10, // Electronics
    2 => 5, // Televisions
    3 => 2, // Tube
    4 => 1, // LCD
    5 => 2, // Plasma
    6 => 5, // Portable Electronics
    7 => 2, // MP3 Players
    8 => 1, // Flash
    9 => 2, // CD Players
    10 => 1 // 2 Way Radios
)

public array tree ( string|array $column [, string $field = null [, string $value = null [, string $having = null ]]] )

Retrieve a full tree, or any parts thereof.

@param $column

The column(s) that you want to return.

@param $field

The $hier->table's column name, or the $having depth below if not specifying a $value.

@param $value

Of the $field. The depths will be relative to this now.

@param $having

The desired depth of the nodes eg. 'depth > 1'

@return

An array of $hier->id (keys) and $column (values), including 'parent' and 'depth' info.

@example
var_export($hier->tree('name'));
array(
    1 => array('name' => 'Electronics', 'parent' => 0, 'depth' => 0),
    2 => array('name' => 'Televisions', 'parent' => 1, 'depth' => 1),
    3 => array('name' => 'Tube', 'parent' => 2, 'depth' => 2),
    4 => array('name' => 'LCD', 'parent' => 2, 'depth' => 2),
    5 => array('name' => 'Plasma', 'parent' => 2, 'depth' => 2),
    6 => array('name' => 'Portable Electronics', 'parent' => 1, 'depth' => 1),
    7 => array('name' => 'MP3 Players', 'parent' => 6, 'depth' => 2),
    8 => array('name' => 'Flash', 'parent' => 7, 'depth' => 3),
    9 => array('name' => 'CD Players', 'parent' => 6, 'depth' => 2),
    10 => array('name' => '2 Way Radios', 'parent' => 6, 'depth' => 2),
)

var_export($hier->tree('name', 'id', 6));
array(
    6 => array('name' => 'Portable Electronics', 'parent' => 1, 'depth' => 0),
    7 => array('name' => 'MP3 Players', 'parent' => 6, 'depth' => 1),
    8 => array('name' => 'Flash', 'parent' => 7, 'depth' => 2),
    9 => array('name' => 'CD Players', 'parent' => 6, 'depth' => 1),
    10 => array('name' => '2 Way Radios', 'parent' => 6, 'depth' => 1),
)

var_export($hier->tree('name', 'depth > 2',));
array(
    8 => array('name' => 'Flash', 'parent' => 7, 'depth' => 3),
)

public array lister ( array $tree [, array $nest = null ] )

Create a multi-dimensional array using the first value of each tree array.

@param $tree

As retrieved from $this->tree().

@example
$tree = $hier->tree('name', 'id', 6);
var_export($hier->lister($tree));
array(
    'Portable Electronics' => array(
        'MP3 Players' => array(
            'Flash',
        ),
        'CD Players',
        '2 Way Radios',
    ),
)

public array nestify ( array $tree )

Create a multi-dimensional array using $this->id's id of each tree array.

@param $tree

As retrieved from $this->tree().

@example
$tree = $hier->tree('name', 'id', 6);
var_export($hier->nestify($tree));
array(
    6 => array(
        7 => array(
            8 => array(),
        ),
        9 => array(),
        10 => array(),
    ),
)

public array flatten ( array $nest [, array $related ] )

Flatten a nested tree.

@param $nest

As retrieved from $this->nestify().

@example
$tree = $hier->tree('name', 'id', 6);
$nest = $hier->nestify($tree);
var_export($hier->flatten($nest));
array(
    array(6, 7, 8),
    array(6, 9),
    array(6, 10),
)
Document Your Code

Installation

Add the following to your composer.json file.

{
    "require": {
        "bootpress/hierarchy": "^1.0"
    }
}

Example Usage

<?php

use BootPress\Database\Component as Database;
use BootPress\Hierarchy\Component as Hierarchy;

$db = new Database('sqlite::memory:');

$db->exec(array(
    'CREATE TABLE category (',
    '    id INTEGER PRIMARY KEY,',
    '    name TEXT NOT NULL DEFAULT "",',
    '    parent INTEGER NOT NULL DEFAULT 0,',
    '    level INTEGER NOT NULL DEFAULT 0,',
    '    lft INTEGER NOT NULL DEFAULT 0,',
    '    rgt INTEGER NOT NULL DEFAULT 0',
    ')',
));

if ($stmt = $db->insert('category', array('id', 'name', 'parent'))) {
    $db->insert($stmt, array(1, 'Electronics', 0));
    $db->insert($stmt, array(2, 'Televisions', 1));
    $db->insert($stmt, array(3, 'Tube', 2));
    $db->insert($stmt, array(4, 'LCD', 2));
    $db->insert($stmt, array(5, 'Plasma', 2));
    $db->insert($stmt, array(6, 'Portable Electronics', 1));
    $db->insert($stmt, array(7, 'MP3 Players', 6));
    $db->insert($stmt, array(8, 'Flash', 7));
    $db->insert($stmt, array(9, 'CD Players', 6));
    $db->insert($stmt, array(10, '2 Way Radios', 6));
    $db->insert($stmt, array(11, 'Apple in California', 1));
    $db->insert($stmt, array(12, 'Made in USA', 11));
    $db->insert($stmt, array(13, 'Assembled in China', 11));
    $db->insert($stmt, array(14, 'iPad', 13));
    $db->insert($stmt, array(15, 'iPhone', 13));
    $db->close($stmt);
}

$hier = new Hierarchy($db, 'category', 'id');

The $db table must have the 'parent', 'level', 'lft', and 'rgt' fields for everything to work. All you need to worry about is the 'id' and 'parent'. This class will take care of the rest. When you get things all set up, and whenever you make any changes:

$hier->refresh();

That will create and update the nested sets info from your 'id' and 'parent' fields. To delete a node and all of it's children you can:

print_r($hier->delete(11)); // array(11, 12, 13, 14, 15)
$hier->refresh(); // don't forget to do this!

You just removed "Apple in California", and everything associated with them.

// Get the id of a given path
echo $hier->id('name', array('Electronics', 'Portable Electronics', 'CD Players')); // 9

// Retrieve a single path
print_r($hier->path('name', 'Flash'));
/*
array(
    1 => 'Electronics',
    6 => 'Portable Electronics',
    7 => 'MP3 Players',
    8 => 'Flash',
)
*/

// Aggregate the total records in a table for each tree node
$db->exec(array(
    'CREATE TABLE products (',
    '    id INTEGER PRIMARY KEY,',
    '    category_id INTEGER NOT NULL DEFAULT 0,',
    '    name TEXT NOT NULL DEFAULT ""',
    ')',
));

if ($stmt = $db->insert('products', array('category_id', 'name'))) {
    $db->insert($stmt, array(3, '20" TV'));
    $db->insert($stmt, array(3, '36" TV'));
    $db->insert($stmt, array(4, 'Super-LCD 42"'));
    $db->insert($stmt, array(5, 'Ultra-Plasma 62"'));
    $db->insert($stmt, array(5, 'Value Plasma 38"'));
    $db->insert($stmt, array(7, 'Power-MP3 128mb'));
    $db->insert($stmt, array(8, 'Super-Shuffle 1gb'));
    $db->insert($stmt, array(9, 'Porta CD'));
    $db->insert($stmt, array(9, 'CD To go!'));
    $db->insert($stmt, array(10, 'Family Talk 360'));
    $db->close($stmt);
}

print_r($hier->counts('products', 'category_id'));
/*
array( // id => count
    1 => 10, // Electronics
    2 => 5, // Televisions
    3 => 2, // Tube
    4 => 1, // LCD
    5 => 2, // Plasma
    6 => 5, // Portable Electronics
    7 => 2, // MP3 Players
    8 => 1, // Flash
    9 => 2, // CD Players
    10 => 1 // 2 Way Radios
)
*/

// Retrieve a tree
$tree = $hier->tree('name', 'id', 6);
print_r($tree);
/*
array(
    6 => array('name' => 'Portable Electronics', 'parent' => 1, 'depth' => 0),
    7 => array('name' => 'MP3 Players', 'parent' => 6, 'depth' => 1),
    8 => array('name' => 'Flash', 'parent' => 7, 'depth' => 2),
    9 => array('name' => 'CD Players', 'parent' => 6, 'depth' => 1),
    10 => array('name' => '2 Way Radios', 'parent' => 6, 'depth' => 1),
)
*/

// Flatten it
$nest = $hier->nestify($tree);
var_export($hier->flatten($nest));
array(
    array(6, 7, 8),
    array(6, 9),
    array(6, 10),
)