SkillAgentSearch skills...

Tree

PHP library for handling tree structures based on parent IDs, e.g. a self-joined database table

Install / Use

/learn @BlueM/Tree
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Build status

Overview

This library provides handling of data that is structured hierarchically using parent ID references. A typical example is a table in a relational database where each record’s “parent” field references the primary key of another record. Of course, usage is not limited to data originating from a database, but anything: you supply the data, and the library uses it, regardless of where the data came from and how it was processed.

It is important to know that the tree structure created by this package is read-only: you can’t use it to perform modifications of the tree nodes.

On the other hand, one nice thing is that it’s pretty fast. This does not only mean the code itself, but also that the constructor takes the input data in a format that is simple to create. For instance, to create a tree from database content, a single SELECT is sufficient, regardless of the depth of the tree and even for thousands of nodes.

Installation

The recommended way to install this library is through Composer: composer require bluem/tree. As the library uses semantic versioning, you can define a version constraint in composer.json which allows all non-major updates.

Alternatively, you can clone the repository using git or download a tagged release.

Usage

Creating a tree

// Create the tree with an array of arrays (or use an array of Iterators,
// Traversable of arrays or Traversable of Iterators):
$data = [
    ['id' => 1, 'parent' => 0, 'title' => 'Node 1'],
    ['id' => 2, 'parent' => 1, 'title' => 'Node 1.1'],
    ['id' => 3, 'parent' => 0, 'title' => 'Node 3'],
    ['id' => 4, 'parent' => 1, 'title' => 'Node 1.2'],
];
$tree = new BlueM\Tree($data);

// When using a data source that uses different keys for "id" and "parent",
// or if the root node ID is not 0 (in this example: -1), use Tree’s argument 2.
$data = [
    ['nodeId' => 1, 'parentId' => -1, 'title' => 'Node 1'],
    ['nodeId' => 2, 'parentId' => 1, 'title' => 'Node 1.1'],
    ['nodeId' => 3, 'parentId' => -1, 'title' => 'Node 3'],
    ['nodeId' => 4, 'parentId' => 1, 'title' => 'Node 1.2'],
];
$tree = new BlueM\Tree(
    $data,
    new Bluem\Tree\Options(rootId: -1, idFieldName: 'nodeId', parentIdFieldName: 'parentId'),
);

Updating the tree with new data

// Rebuild the tree from new data
$tree->rebuildWithData($newData);

Retrieving nodes

// Get the top-level nodes (returns array)
$rootNodes = $tree->getRootNodes();

// Get all nodes (returns array)
$allNodes = $tree->getNodes();

// Get a single node by its unique identifier
$node = $tree->getNodeById(12345);

Getting a node’s parent, siblings, children, ancestors and descendants

// Get a node's parent node (will be null for the root node)
$parentNode = $node->getParent();

// Get a node's siblings as an array
$siblings = $node->getSiblings();

// Ditto, but include the node itself (identical to $node->getParent()->getChildren())
$siblings = $node->getSiblingsAndSelf();

// Get a node's preceding sibling (null, if there is no preceding sibling)
$precedingSibling = $node->getPrecedingSibling();

// Get a node's following sibling (null, if there is no following sibling)
$followingSibling = $node->getFollowingSibling();

// Does the node have children?
$bool = $node->hasChildren();

// Get the number of Children
$integer = $node->countChildren();

// Get a node's child nodes
$children = $node->getChildren();

// Get a node's ancestors (parent, grandparent, ...)
$ancestors = $node->getAncestors();

// Ditto, but include the node itself
$ancestorsPlusSelf = $node->getAncestorsAndSelf();

// Get a node's descendants (children, grandchildren, ...)
$descendants = $node->getDescendants();

// Ditto, but include the node itself
$descendantsPlusSelf = $node->getDescendantsAndSelf();

Accessing a node’s properties

// Get a node's ID
$id = $node->getId();

// Get the node's hierarchical level (1-based)
$level = $node->getLevel();

// Access node properties using get() overloaded getters or __get():
$value = $node->get('myproperty');
$value = $node->myproperty;
$value = $node->getMyProperty();

// Get the node's properties as an associative array
$array = $node->toArray();

// Get a string representation (which will be the node ID)
echo "$node";

Using the library with non-default options

Tree uses defaults, which are defied in \BlueM\Tree\Options. If any of those defaults does not fit your needs, you can customize it.

For example, if the field which holds the parent node’s ID in your data is not called “parent”, but “parent_id”, you could specify it like this:

$tree = new BlueM\Tree($nodesData, new \BlueM\Tree\Options(parentIdFieldName: 'parent_id'));

Example: Using it with literal data

<?php

require 'vendor/autoload.php';

// Create the Tree instance
$tree = new BlueM\Tree([
    ['id' => 1, 'name' => 'Africa'],
    ['id' => 2, 'name' => 'America'],
    ['id' => 3, 'name' => 'Asia'],
    ['id' => 4, 'name' => 'Australia'],
    ['id' => 5, 'name' => 'Europe'],
    ['id' => 6, 'name' => 'Santa Barbara', 'parent' => 8],
    ['id' => 7, 'name' => 'USA', 'parent' => 2],
    ['id' => 8, 'name' => 'California', 'parent' => 7],
    ['id' => 9, 'name' => 'Germany', 'parent' => 5],
    ['id' => 10, 'name' => 'Hamburg', 'parent' => 9],
]);
...
...

Example: Using it with a self-joined database table

<?php

require 'vendor/autoload.php';

// Database setup (or use Doctrine or whatever ...)
$db = new PDO(...);

// SELECT the records in the sort order you need
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title');
$records = $stm->fetchAll(PDO::FETCH_ASSOC);

// Create the Tree instance
$tree = new BlueM\Tree($records);
...
...

JSON serialization

As Tree implements JsonSerializable, a tree can be serialized to JSON. By default, the resulting JSON represents a flat (non-hierarchical) representation of the tree data, which – once decoded from JSON – can be re-fed into a new Tree instance. In version before 3.0, you had to subclass the Tree and the Node class to customize the JSON output. Now, serialization is extracted to an external helper class which can be changed both by setting a constructor argument or at runtime just before serialization. However, the default serialization result is the same as before, so you won’t notice any change in behavior unless you tweaked JSON serialization.

To control the JSON, you can either pass an option jsonSerializer to an \BlueM\Tree\Options instance you pass the the Tree constructor, which must be an object implementing \BlueM\Tree\Serializer\TreeJsonSerializerInterface. Or you call method setJsonSerializer() on the tree. The latter approach can also be used to re-set serialization behavior to the default by calling it without an argument.

The library comes with two distinct serializers: \BlueM\Tree\Serializer\FlatTreeJsonSerializer is the default, which is used if no serializer is set and which results in the “old”, flat JSON output. Plus, there is \BlueM\Tree\Serializer\HierarchicalTreeJsonSerializer, which creates a hierarchical, depth-first sorted representation of the tree nodes. If you need something else, feel free to write your own serializer.

Handling inconsistent data

If a problem is detected while building the tree (such as a parent reference to the node itself or in invalid parent ID), an exception (subclass of InvalidParentException) is thrown. Often this makes sense, but it might not always. For those cases, you can pass in a callable by setting $buildWarningCallback of a \BlueM\Tree\Options instance which can be given as argument 2 to Tree’s constructor, and which will be called whenever a problem is seen. The signature of the callable is like this:

function yourCallback(MissingNodeInvalidParentException $exception, Tree $tree, Node $node, mixed $parentId) {
   // Log this, throw your own exception, ignore this, ...
}

Running Tests

PHPUnit is configured as a dev dependency, so running tests is a matter of:

  • composer install
  • composer test
  • composer test-coverage (For generating coverage output, requires xDebug.)

Version History

4 (2025-08-17)

  • Breaking change: minimum PHP version is 8.2
  • Breaking change: Node properties are no longer normalized to lowercase
  • Breaking change: The argument to Node::get($propertyName) is now treated case-sensitively
  • Breaking change: $node->propertyName is now case-sensitive
  • No breaking change: Using as getter for accessing a property’s value is still case-insensitive, as functions are case-insensitive in PHP. So if you have a property foo on a node, you can call $node->getFoo() or $node->getFOO(). However, if you have property names which differ only in case (admittedly, an extreme edge case), the property with the exactly matching name is preferred over the case-insensitive one.
  • Breaking change: The signature for Node::__construct() has changed. The parent ID is no longer passed as 2nd argument – this was redundant, as the parent is available in $node->parent.
  • Breaking change: argument 2 for Tree’s constructor is no longer an associative array, but a DTO, which ensures correct naming and type-safety.
  • Breaking change (if you subclassed it): The signature for Tree::createNode() has changed, as the iterable containing the node properties is now optional.
  • Breaking change: Calling the getter for a non-exis

Related Skills

View on GitHub
GitHub Stars241
CategoryData
Updated3mo ago
Forks46

Languages

PHP

Security Score

77/100

Audited on Dec 3, 2025

No findings