bluem / tree Goto Github PK
View Code? Open in Web Editor NEWPHP library for handling tree structures based on parent IDs, e.g. a self-joined database table
PHP library for handling tree structures based on parent IDs, e.g. a self-joined database table
When encountering a build issue, I want to log an error instead of throwing an exception because the rest of the tree is still usable. The default handler checks if a parent is valid by using $this->nodes
which is not available or passed to the callback at build time.
Hello @BlueM , can you add support version 8 php?
I use this package in project laravel, but i can't use last version laravel and nova.
A few times, I had the need to rebuild the tree with fresh data. This can be easily done, but currently requires creating a new instance, as there is no public API for refreshing the data. Creating a new instance is simple, but if instantiation can be avoided, this is a win.
Another thought: could it be useful to enable Tree
to fetch (or refetch) data instead of pushing the data into the constructor (or in some other method)?
Hi,
Thanks a lot for your wonderfull tool, it works very well. Nevertheless i have a question, after creating the tree and outputting the json hierarchical tree i have:
{
"14": {
"name": "2 Vous \u00eates",
"title": "2 Vous \u00eates",
"id": 2,
"parent": 0,
"children": {
"0": {
"name": "291 Etes-vous enceinte ?",
"title": "291 Etes-vous enceinte ?",
"id": 291,
"parent": 2
}
}
},
...
nevertheless, what i would like to obtain is somthing formatted like this:
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{"name": "AgglomerativeCluster", "value": 3938},
{"name": "CommunityStructure", "value": 3812},
{"name": "HierarchicalCluster", "value": 6714},
{"name": "MergeEdge", "value": 743}
]
},
...
Is this possible ?
Thanks a lot
Just an idea: would an iterator – to traverse the whole tree or part of the tree – be useful, in addition to the iteration methods currently available?
How to send children as json
if can improve to get all children to selected level
I didn't find any method to deal with my problem, but maybe do you know any easy modification I can make to get only leaf nodes from certain node or root?
Since version 2.0, the method Node::getAncestorsGeneric
does not return the last ancestor of the set of nodes. In version 1.5.3, the method did not exist and the original method looked like this :
public function getAncestors($includeSelf = false)
{
$ancestors = $includeSelf ? array($this) : array();
if (null === $this->parent) {
return $ancestors;
}
return array_merge($ancestors, $this->parent->getAncestors(true));
}
It works fine, since if the current node has no parent, the recursive iteration ends and the current node is returned. Since 2.0, the method getAncestors
uses the method getAncestorsGeneric
which looks like this :
protected function getAncestorsGeneric(bool $includeSelf): array
{
if (null === $this->parent) {
return [];
}
return array_merge($includeSelf ? [$this] : [], $this->parent->getAncestorsGeneric(true));
}
This means that if the current node has no parent, an empty array will be returned instead of the current node. This is fine if $includeSelf equals false, but if I have a data set with for example 2 nodes, 1 root and 1 child, the method will ultimately return an empty array despite the fact that $includeSelf equals true at some point.
My data:
{
"id": "moblie_prj",
"name": "my project",
"parent": "0"
},
this data throw exception Node with ID $id references its own ID as parent ID
I found that Tree.php (line 188) compare of two string should be ===
instead of ==
.
Including code modernization (short array syntax, …)
I noticed that there is a method that brings the level of the category.
Is there any way to return to the maximum depth?
I work with data sets that come from other applications that I don't/can't control. The parent
field of my data is id_parent
and iterating over my data and renaming the filed seems a little bit of an overkill. Can you make the parent
field configurable? (As in make it possible to specify what the parent
field should be called).
Node::Get
why throw an exeption and not null? I've the problem there are parents with an propertie or the propertie is unset. If I initialize it with null and call the get-method from the node I get the exeption
Best regrets
Magnus
Looks like currenty a tree can only be built from array of arrays:
protected function createNode($id, $parent, array $properties): Node
{
return new Node($id, $parent, $properties);
}
It would be great if tree could be created from array of ArrayAccess
objects.
Personally I use my own tree implementation. But now I want to switch to something modern and community driven.
I looked at nicmart/tree
, but looks like it is not maintained.
This projects seems to be maintained and updating to new PHP versions, I would like to use it, but lack of described functionality prevents me from it :(
I always create trees from array of objects (just looked over my last projects - I never build a tree from array of arrays).
public function __construct(array $data, array $options = array())
{
$this->options = array_change_key_case($options, CASE_LOWER);
if (!isset($this->options['rootid'])) {
$this->options['rootid'] = 0;
}
$this->build($data, $options);
}
Your changing the case of the options index but passing down the original options , should be
$this->build($data, $this->options);
Is it possible to duplicate child of a node with all its subchildren structure? Give example please.
Hi @BlueM,
When I serialized object in Tree with properties in camelCase, I cant unserialised them because Node
change key to lowercase.
So I ask you : what is the benefit to change key to lowercase here ?
Line 41 in e520150
I try to change the line with that fix and it's works :
$this->properties = $properties;
Currently, it is required to:
Would be nicer to get along without subclassing at all. Basic concept could be: externalize the serialization, offer several serialization strategies (and pick a default strategy which produces the same JSON as the current implementation) and make them selectable.
Motivation: see #16.
I think the project will benefit from using constants instead of magic strings when declaring options. This will allow flexibility when renaming options internally in the future, as well as avoiding all issues related to magic strings, both for people contributing to the library and those using it.
I was planning on making a PR, but I figured I should open the discussion first.
I was thinking of making a class that would looking something like this (I only added the options I'm personally using in my project):
<?php
namespace BlueM\Tree;
class Options
{
const PARENT_KEY = 'parent';
const ID_KEY = 'id';
const ROOT_ID = 'rootid';
}
This would allow implementations to change like this:
$tree = new Tree(
$data,
[
- 'id' => 'name',
- 'rootId' => null,
]
);
$tree = new Tree(
$data,
[
+ Tree\Options::ID_KEY => 'name',
+ Tree\Options::ROOT_ID => null,
]
);
And the constructor method for Tree would change slightly (I don't have a diff for this because I was working locally on my vendor folder, as a proof of concept)
if (!empty($options[Options::ID_KEY])) {
if (!\is_string($options[Options::ID_KEY])) {
throw new \InvalidArgumentException('Option “ID_KEY” must be a string');
}
$this->idKey = $options[Options::ID_KEY];
}
This would maintain backward compatibility.
Thoughts?
I get my list from an external API, and it, unfortunately, contains parentless nodes. So when building a tree, it keeps throwing an exception "Node with ID $id points to non-existent parent with ID $pid".
What's the best approach for ignoring these?
how to write to array?
BlueM\Tree\Node Object
(
[properties:protected] => Array
(
[title] => 点心/蛋糕
[id] => 1
[parent] => 0
)
[parent:protected] => BlueM\Tree\Node Object
(
[properties:protected] => Array
(
[id] => 0
[parent] =>
)
[parent:protected] =>
[children:protected] => Array
(
[0] => BlueM\Tree\Node Object
)
)
Hello, I need to check when I already visited a node when traversing through a tree. In the array of arrays that I used to create the tree I added a "visited" key to each node but when I try to update said key
$node->visited = true
the node isn't updating the value of the visited property, I don't know if this is the expected behavior and if so, is there any native way to check if the node has already been visited?
Nodes (maybe also the whole tree) should include a sensible default implementation of JsonSerializable
to make them JSON-serializable.
$node->toArray() returns only 'id' & 'parent'. Is it intentional? Thank you.
I have an error when try to use json_encode with $tree->getRootNodes()
This error can be hidden on my develop machine, but the server doesn't return nothign even error
... if nothing is found, instead of throwing an exception.
Branched from discussion in: #22
There exists a need to provide the ability to require validation of the provided data against an expected schema. By default, this should throw a new MalformedDataException
(or similar) that stores information about which data attribute was missing or was otherwise malformed (i.e. key is a string
instead of int
), and in what way it was missing or malformed.
Performing malformed-data error checking should be enabled by default, but easily disabled. This is necessary to maintain performance on very large data sets that you know will have the right structure, such as the results of a well-written SQL statement using PDO::FETCH_ASSOC
or similar.
Here is good discussion of options to validate an array against a schema: https://stackoverflow.com/questions/4036708/how-can-i-validate-the-structure-of-my-php-arrays
// Somewhere around L216 in Tree.php, immediately inside of "foreach ($data as $rows)"
if ($this->malformedDataHandler !== false)
{
$malformedData = array_diff(array_merge_recursive($arrCandidate, $arrModel), $arrModel));
if (sizeof($malformedData))
{
call_user_func($this->malformedDataHandler, $row);
}
}
I suggest putting it inside of the foreach so that the entire dataset doesn't have to be traversed twice. Once to get the $malformedData
, if any, and then again to process it. It would be worth testing the tradeoff of checking for malformed data all at once, which would require a full re-iteration of the array, but that may end up actually being quicker than doing the array_merge_recursive()
and array_diff()
over every node inline.
The above suggestion would allow the user to set $malformedDataHandler = false;
and disable checking/handling of the data vs the schema, and just allow PHP to log its E_NOTICE
given that malformed data is encountered.
ErrorException
in \vendor/bluem/tree/src/Tree.php (line 235)
230 $row = iterator_to_array($row);
231 }
232 $this->nodes[$row[$this->idKey]] = $this->createNode(
233
234 $row[$this->idKey],
235 $row[$this->parentKey],
236 $row
237 );
238
239 if (empty($children[$row[$this->parentKey]])) {
240
$children[$row[$this->parentKey]] = [$row[$this->idKey]];
Hello can you help me?
Hello,
i have mysql table structured for tree where each node have maximum 3 nodes in each level.
Example node1 is in row=1 & column=1
node 1 has 3 childrens where row=2 & column=1,=2, =3
next row is row=3 and have 9 possibles of column from col=1 to col=9
How i can use your code to insert new node in his right position if i know the parent
Exmaple if i want insert new node for parent node1(row=1, col=1) the logic must be row=3 & col=1
in my db structure each node saved in 1 record with data: id, id_parent, row, column,id_reffer
id_parent is the parent id of direct sub level
id_reffer is the parent id first level
Can help me? Thanks
\BlueM\Tree::getNodeById() returns non-existent nodes:
$data = [];
$sut = new BlueM\Tree($data);
// #1: returns a "\BlueM\Tree\Node" (incorrect)
var_dump($sut->getNodeById(0));
// #2: throws InvalidArgumentException (correct)
var_dump($sut->getNodeById(1));
Expected behavior: when an id does not exist, an \InvalidArgumentException should be thrown.
Details
There seems to be a problem with 0 (int)
, '0' (string)
and false (bool)
:
// arrange
$data = [];
$sut = new BlueM\Tree($data);
$cases = [-1, 0, 1, '-1', '0', '1', PHP_INT_MIN, PHP_INT_MAX, true, false, null];
foreach ($cases as $case) {
try {
// act
$result = $sut->getNodeById($case)->toArray();
// assert
assert(!is_array($result), 'fail: FIXME');
} catch (InvalidArgumentException $e) {
var_dump(['case' => $case, 'result' => 'pass']);
} catch (Throwable $e) {
var_dump(['case' => $case, 'result' => $e->getMessage()]);
}
}
Result:
array (size=2)
'case' => int -1
'result' => string 'pass' (length=4)
array (size=2)
'case' => int 0
'result' => string 'fail: FIXME' (length=11)
array (size=2)
'case' => int 1
'result' => string 'pass' (length=4)
array (size=2)
'case' => string '-1' (length=2)
'result' => string 'pass' (length=4)
array (size=2)
'case' => string '0' (length=1)
'result' => string 'fail: FIXME' (length=11)
array (size=2)
'case' => string '1' (length=1)
'result' => string 'pass' (length=4)
array (size=2)
'case' => int -9223372036854775808
'result' => string 'pass' (length=4)
array (size=2)
'case' => int 9223372036854775807
'result' => string 'pass' (length=4)
array (size=2)
'case' => boolean true
'result' => string 'pass' (length=4)
array (size=2)
'case' => boolean false
'result' => string 'fail: FIXME' (length=11)
array (size=2)
'case' => null
'result' => string 'pass' (length=4)
Hi,
Sorry if I'm missing something - but I have an array of arrays, passed into the Tree constructor - ids and parent ids set correctly, not getting any errors.
But when I json_encode the result.. it's just a flat array as it was when I passed it? There's no hierarchy, no nesting of nodes?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.