stefano/stefano-tree

Framework agnostic library for managing tree structures

3.0.1 2018-01-15 16:11 UTC

README

Latest Stable Version Test Status Coverage Status Scrutinizer Code Quality Dependency Status License Total Downloads

Donate on PayPal

Nested Set implementation for PHP.

Live demo

Features

  • NestedSet(MPTT - Modified Pre-order Tree Traversal)
  • Support scopes (multiple independent tree in one db table)
  • Rebuild broken tree
  • Tested with MySQL/MariaDB and PostgreSQL but should work with any database vendor which support transaction
  • Supported Frameworks Zend Framework 1, Zend Framework 2, Doctrine 2 DBAL. It is easy to implement support for any framework

Dependencies

  • This library has no external dependencies only PHP and your favorite framework is required

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
sequenceName string see note Required for PostgreSQL
scopeColumnName string see note If empty scope support is disabled
  • Use static factory method
use \StefanoTree\NestedSet;

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

$dbAdapter = Zend2 Db Adapter or Zend1 Db Adapter or Doctrine DBAL Connection

$tree = NestedSet::factory($options, $dbAdapter);
  • or create tree adapter directly
use \StefanoTree\NestedSet;
use \StefanoTree\NestedSet\Options;
use \StefanoTree\NestedSet\Adapter\Zend2 as Zend2TreeDbAdapter;

$options = new Options(array(...);
$dbAdapter = ... supported db adapter ...
$nestedSetAdapter = new Zend2TreeDbAdapter($options, $dbAdapter);
$tree = new NestedSet($nestedSetAdapter);
  • You can join table. Example is for Zend Framework 2 but it works similar for other supported frameworks.
use Zend\Db\Sql\Select;

$selectBuilder = function() {
    // @see https://docs.zendframework.com/zend-db/sql/#select
    $select = new Select('tree_traversal');
    $select->join(
        array('metadata'),
        'metadata.tree_traversal_id = tree_traversal.tree_traversal_id',
        array('metadata' => 'name'),
        $select::JOIN_LEFT
        );
    
    return $select;    
}

// as $selectBuilder you can use any "callable" like function or object
$tree->getAdapter()->setDbSelectBuilder($selectBuilder);

API

Creating nodes

  • Create root node
use StefanoTree\Exception\ValidationException;

try {
    $data = array(
        // values
    );
    
    // 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();
}    
  • Create new node. You can create new node at 4 different locations.

placements

use StefanoTree\Exception\ValidationException;

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

    $nodeId = $tree->addNodePlacementTop($targetNodeId, $data, $tree::PLACEMENT_CHILD_TOP);
    $nodeId = $tree->addNodePlacementChildBottom($targetNodeId, $data, $tree::PLACEMENT_CHILD_BOTTOM);
    $nodeId = $tree->addNodePlacementTop($targetNodeId, $data, $tree::PLACEMENT_TOP);
    $nodeId = $tree->addNodePlacementBottom($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

  • You can move node at 4 different locations.

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

  • Get descendants
$nodeId = 15;

// all descendants
$tree->getDescendantsQueryBuilder()
     ->get($nodeId);
     
// only children     
$tree->getDescendantsQueryBuilder()
     ->excludeFirstNLevel(1)
     ->levelLimit(1)
     ->get($nodeId);

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

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

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

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

// get all
$tree->getAncestorsQueryBuilder()
     ->get($nodeId);

// 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

  • Check if tree is valid
use StefanoTree\Exception\ValidationException;

try {
    $satus = $tree->isValid($rootNodeId);
} catch (ValidationException $e) {
    $errorMessage = $e->getMessage();
}
  • Rebuild broken tree
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.