stefano-tree

Tree

Latest Stable Version Test application Coverage Status License Total Downloads Monthly Downloads

Buy me a coffee

Donate on PayPal

Nested Set implementation for PHP.

Live demo

Features

Dependencies

Installation

Run following command in terminal

composer require stefano/stefano-tree

Create Tree Adapter

key type required default value note
tableName string yes    
idColumnName string yes    
leftColumnName string no lft  
rightColumnName string no rgt  
levelColumnName string no level  
parentIdColumnName string no parent_id  
scopeColumnName string see note   If empty scope support is disabled
dbSelectBuilder callable no   see Join table example below
use \StefanoTree\NestedSet;

$options = array(
    'tableName'    => 'tree_traversal',
    'idColumnName' => 'tree_traversal_id',
    // other options
);

$dbAdapter = pure \PDO, Laminas Db Adapter, Doctrine DBAL Connection or any class which implements StefanoTree\NestedSet\Adapter\AdapterInterface interface 

$tree = new NestedSet($options, $dbAdapter);
$options = array(
    'tableName'       => 'tree_traversal',
    'idColumnName'    => 'tree_traversal_id',
    'dbSelectBuilder' => function() {
         // You can use any "callable" like function or object
         // Select must be without where or order part
         return 'SELECT tree_traversal.*, m.something, ...'
           .' FROM tree_traversal'
           .' LEFT JOIN metadata AS m ON tree_traversal.id=m.tree_id';
     }, 
    // other options
);

$tree = new NestedSet($options, $dbAdapter);

API

Creating nodes

use StefanoTree\Exception\ValidationException;

try {
    $data = array(
        // values
        // id_column_name => uuid 
    );
    
    // create root node.
    $rootNodeId = $tree->createRootNode($data);
    
    // create root node. Second param "$scope" is required only if scope support is enabled.
    $rootNodeId = $tree->createRootNode($data, $scope);    
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}    

placements

use StefanoTree\Exception\ValidationException;

try {
    $targetNodeId = 10;
    
    $data = array(
        // values
        // id_column_name => uuid 
    );

    $nodeId = $tree->addNode($targetNodeId, $data, $tree::PLACEMENT_CHILD_TOP);
    $nodeId = $tree->addNode($targetNodeId, $data, $tree::PLACEMENT_CHILD_BOTTOM);
    $nodeId = $tree->addNode($targetNodeId, $data, $tree::PLACEMENT_TOP);
    $nodeId = $tree->addNode($targetNodeId, $data, $tree::PLACEMENT_BOTTOM);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}    

Update Node

use StefanoTree\Exception\ValidationException;

try {
    $targetNodeId = 10;
    
    $data = array(
        // values
    );
    
    $tree->updateNode($targetNodeId, $data);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}    

Move node

placements

use StefanoTree\Exception\ValidationException;

try {
    $sourceNodeId = 15;
    $targetNodeId = 10;
    
    $tree->moveNode($sourceNodeId, $targetNodeId, $tree::PLACEMENT_CHILD_TOP);
    $tree->moveNode($sourceNodeId, $targetNodeId, $tree::PLACEMENT_CHILD_BOTTOM);
    $tree->moveNode($sourceNodeId, $targetNodeId, $tree::PLACEMENT_TOP);
    $tree->moveNode($sourceNodeId, $targetNodeId, $tree::PLACEMENT_BOTTOM);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}        

Delete node or branch

use StefanoTree\Exception\ValidationException;

try {
    $nodeId = 15;
    
    $tree->deleteBranch($nodeId);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}    

Getting nodes

$nodeId = 15;

// node data or null if the node does not exist
$node = $tree->getNode($nodeId);

// root node of the tree (pass $scope if scope support is enabled)
$rootNode = $tree->getRootNode($scope);

// all root nodes (useful when scope support is enabled)
$roots = $tree->getRoots();
$nodeId = 15;

// all descendants
$tree->getDescendantsQueryBuilder()
     ->get($nodeId);
     
// all descendants result as nested array
$tree->getDescendantsQueryBuilder()
     ->get($nodeId, true);
     
// only children     
$tree->getDescendantsQueryBuilder()
     ->excludeFirstNLevel(1)
     ->levelLimit(1)
     ->get($nodeId);

// exclude the first level ($nodeId itself) from result
$tree->getDescendantsQueryBuilder()
     ->excludeFirstNLevel(1)
     ->get($nodeId);

// exclude first two levels from result
$tree->getDescendantsQueryBuilder()
     ->excludeFirstNLevel(2)
     ->get($nodeId);

// return first 4 levels
$tree->getDescendantsQueryBuilder()
     ->levelLimit(4)
     ->get($nodeId);

// exclude branch from result
$tree->getDescendantsQueryBuilder()
     ->excludeBranch(22)
     ->get($nodeId);
$nodeId = 15;

// get all
$tree->getAncestorsQueryBuilder()
     ->get($nodeId);
     
// get all as nested array
$tree->getAncestorsQueryBuilder()
     ->get($nodeId, true);

// exclude last node ($nodeId) from result
$tree->getAncestorsQueryBuilder()
     ->excludeLastNLevel(1)
     ->get($nodeId);

// exclude first two levels from result
$tree->getAncestorsQueryBuilder()
     ->excludeFirstNLevel(2)
     ->get($nodeId);

Validation and Rebuild broken tree

use StefanoTree\Exception\ValidationException;

try {
    $status = $tree->isValid($rootNodeId);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}

if (false === $status) {
    // the tree is broken and can be repaired, see rebuild() below
}
use StefanoTree\Exception\ValidationException;

try {
    $tree->rebuild($rootNodeId);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}

Contributing

Any contributions are welcome. If you find any issue don’t hesitate to open a new issue or send a pull request.

Buy me a coffee