skip to Main Content

I have a list of objects, each object could belong as a child of another object and each one has an element (‘parent_id’) that specifies which object it’s a child of.

An example of what the expected tree should look like after children are properly imbedded:

Current Array
element 1
element 2
element 3
element 4
element 5

Organized into a hierarchy 
-----------------------

element 2
   -children:
         element 1
             -children
                  element 5
element 3
   -children:
         element 4

There should be two top elements, the first element would contain the element1 as a child, which would itself contain element 5 as a child. The third element would contain element 4 as a child.

The final result should produce a proper tree structure from it, however I’m running into an issue that when I generate the tree some of the elements are null.

From what I understand it is because of the weird scoping rules PHP has and that the last reference in the loop remains bound even after the end of the loop. So I included the unset($var) statements after each loop, however I’m still getting null values pushed into the tree.

Here is the fully contained example of the code:

$response = array(
    array( "kind"=> "t1", "data" => array( "id" => 25, "parent_id" => 30 )),
    array("kind"=> "t1", "data" => array( "id" => 30,"parent_id" => 0)),
    array("kind"=> "t1", "data" => array("id" => 32, "parent_id" => 0 )),
    array("kind"=> "t1", "data" => array("id" => 33,"parent_id" => 32)),
    array("kind"=> "t1", "data" => array("id" => 35,"parent_id" => 25))
);

$json_str = json_encode($response);

$tree = array();
foreach($response as &$firstObj)
{
    $parentFound = null;
    foreach($response as &$secondObject)
    {
        if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
        {
            $parentFound = &$secondObject;
            break;
        }
        
    }
    unset($secondObject);

    if($parentFound)
    {
        $parentFound['data']['children'] = array(&$firstObj);
    }
    else{
        $tree[] = $firstObj;
    }

}
unset($firstObj);

print_r($tree);

The expected tree should contain only the topmost elements that are not children of other elements, the children should be embedded through references into the appropriate spaces of the top tree elements.

2

Answers


  1. Chosen as BEST ANSWER

    I found the solution with the help of GPT/Bing:

    So while I was unsetting the other variables, I wasn't unsetting the $parentFound, which has to be done as well.

    Another thing was that I was not saving the item by reference when saving the item to the tree, which also has to be done in order for the whole reference tree to work.

    The final code is:

    $json_str = json_encode($response);
    $tree = array();
    
    foreach($response as &$firstObj)
    {
        $parentFound = null;
        foreach($response as &$secondObject)
        {
            if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
            {
                $parentFound = &$secondObject;
                break;
            }
        }
    
        if($parentFound)
        {
            $parentFound['data']['children'] = array(&$firstObj);
        }
        else{
            $tree[] = &$firstObj; //has to be passed by reference
        }
        unset($secondObject);
        unset($parentFound);  //have to also include the $parentFound in the unset 
    }
    unset($firstObj);
    print_r($tree);
    

  2. While I would probably opt for a recursive approach on a professional project, I’ve managed to produce a non-recursive approach using references like your posted code but with a single loop.

    Code: (Demo)

    $result = [];
    foreach ($array as $obj) {
        $id = $obj['data']['id'];
        $parentId = $obj['data']['parent_id'];
        if (isset($ref[$id])) {  // child array populated before parent encountered
            $obj['children'] = $ref[$id]['children'];  // don't lose previously stored data
        }
        $ref[$id] = $obj;
        
        if (!$parentId) {
            $result[] = &$ref[$id];  // push top-level reference into tree
        } else {
            $ref[$parentId]['children'][] = &$ref[$id]; // push into parent-specific collection of references
        }
    }
    var_export($result);
    

    For a stacking process (not a recursive one) to build a hierarchical structure, your sample data is a little unexpected in that a child id integer is less than a parent id. I mean, in nature, parents are born first and then children are born. With your sample data, the input could not be pre-sorted by id value before looping — this would have ensured that all parents where declared before children were encountered.

    As a consequence, my snippet needs to push previously encountered children data into a newly encountered parent so that the declaration of the parent as a reference does not erase the cached children data.

    I extended your sample data by one extra row while testing. If you find any breakages with my script, please supply new test data that exposes the issue.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search