Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP - How to access a deep array's contents, building the path to it dynamically

I am trying to build a hierarchical array in PHP, from relational database contents that are stored using a closure table. For a given result, I will have the full path to the LEAF node, the below would look like my result set.

1~root~the root node

1~root~the root node>>>2~category1~First category

1~root~the root node>>>3~category2~Second category

1~root~the root node>>>2~category1~First category>>>4~subCatOfCategory1~SubCategory of Cat 1

Anyway, those are my database results. So I want to traverse them and build a hierarchical structure in PHP so I can convert it to JSON and render a tree in DOJO

So as I walk through each row, I am building a "path" to the leaf because I only need to add an element to tree when the element is a "leaf"... Along that thinking I decided that I would tokenize each result, using ">>>" as the delimiter, giving me the nodes that are in that row. Then I loop through those nodes, tokenizing each one by "~" which gives me the attributes of each node.

So, I have a for loop to process each ROW and it basically determines that if the node being processed is NOT a leaf, add it's ID to an array that is to track the path to get to the eventual leaf that will be processed. THEN, when I finally do arrive at the LEAF, I can call a function to insert a node, using the PATH that I've compiled along the way.

Hopefully that all makes sense.. so I've included the code below.. Consider the second result from above. When I have processed that entire result and am about to call the function insertNodeInTreeV2(), the arrays look as below...

$fullTree is an array with 1 element, indexed at [1] That element contains an array with four elements: ID(1), NAME(root), Description(the root node), CHILDREN(empty array)

$pathEntries is an array with only one element, (1). That is to mean that the PATH to the LEAF node being inserted is by node [1], which is the root node.

$nodeToInsert is an array with four elements: ID(2), NAME(category1), Description(First Category), CHILDREN(empty array)

$treeRootPattern is a STRING that contains the Variable name I'm using to store the whole array/tree, which in this case is "fullTree".

private function insertNodeInTreeV2( array &$fullTree, array $pathEntries, array $nodeToInsert, $treeRootPattern )
{
  $compiledPath = null;
  foreach ( $pathEntries as $path ) {
    $compiledPath .= $treeRootPattern . '[' . $path . '][\'CHILDREN\']';
  }
  // as this point $compiledPath = "fullTree[1]['CHILDREN']"
  $treeVar = $$compiledPath;
}

So when I make the assignment, $treeVar = $$compiledPath;, I THINK I am setting the variable $treeVar to be equal to $fullTree[1]['CHILDREN'] (which I have verified in my debugger is a valid array index). Even if I paste the contents of $compiledPath into a new Expression in Eclipse debugger, it shows me an empty array, which makes sense because that is what's located in $fullTree[1]['CHILDREN']

But instead, the runtime is telling me the following error...

troller.php line 85 - Undefined variable: fullTree[1]['CHILDREN']

Any help on this would be greatly appreciated... And if you have a better way for me to get from the result set I described to the hierarchical array I'm trying to build, I'd be eager to adopt a better method.

UPDATED TO ADD THE CODE THAT CALLS THE ABOVE FUNCTION -- THE FOR LOOP PROCESSES ROWS OF DATABASE RESULTS, AS DESCRIBED ABOVE

foreach ( $ontologyEntries as $entry ) {

        // iterating over rows of  '1~~root~~The root node>>>2~~category1~~The first category
        $nodes = explode( '>>>', $entry['path'] );
        $numNodes = count( $nodes ) - 1 ;

        $pathToNewNode = null;  // this is the path, based on ID, to get to this *new* node
        for ( $level = 0; $level <= $numNodes; $level++ ) {

            // Parse the node out of the database search result
            $thisNode = array(
                'ID'          => strtok($nodes[$level], '~~'),  /*   1   */
                'NAME'        => strtok( '~~'),                 /*   Root   */
                'DESCRIPTION' => strtok( '~~'),                 /*   This is the root node   */
                'CHILDREN'    => array()
            );

            if ( $level < $numNodes ) {   // Not a leaf, add it to the pathToThisNodeArray
                $pathToNewNode[] = $thisNode['ID'];
            }
            else {
                // processing a leaf, add it to the array
                $this->insertNodeInTreeV2( $$treeRootPattern,  $pathToNewNode, $thisNode, $treeRootPattern );
            }

        }

    }
like image 538
rogodeter Avatar asked Apr 04 '12 22:04

rogodeter


1 Answers

See my comments below your question for an explanation.

$paths = array(
    "1~root~the root node",
    "1~root~the root node>>>2~category1~First category",
    "1~root~the root node>>>3~category2~Second category",
    "1~root~the root node>>>2~category1~First category>>>4~subCatOfCategory1~SubCategory of Cat 1"
);

$tree = array();

foreach ($paths as $path)
{
    $currentNode = &$tree;

    $parts = explode(">>>", $path);

    foreach ($parts as $part)
    {
         $node = explode("~", $part);

         // create all nodes along this path
         if (!isset($currentNode[$node[0]]))
         {
              $currentNode[$node[0]] = array(
                "ID"            => $node[0],
                "NAME"          => $node[1],
                "DESCRIPTION"   => $node[2],
                "CHILDREN"      => array(),
              );
         }

         $currentNode = &$currentNode[$node[0]]["CHILDREN"];
    }
}

var_dump($tree);

Outputs:

array
  1 => 
    array
      'ID' => string '1' (length=1)
      'NAME' => string 'root' (length=4)
      'DESCRIPTION' => string 'the root node' (length=13)
      'CHILDREN' => 
        array
          2 => 
            array
              'ID' => string '2' (length=1)
              'NAME' => string 'category1' (length=9)
              'DESCRIPTION' => string 'First category' (length=14)
              'CHILDREN' => 
                array
                  4 => 
                    array
                      'ID' => string '4' (length=1)
                      'NAME' => string 'subCatOfCategory1' (length=17)
                      'DESCRIPTION' => string 'SubCategory of Cat 1' (length=20)
                      'CHILDREN' => &
                        array
                          empty
          3 => 
            array
              'ID' => string '3' (length=1)
              'NAME' => string 'category2' (length=9)
              'DESCRIPTION' => string 'Second category' (length=15)
              'CHILDREN' => 
                array
                  empty

The loop will create all nodes that are included in a path, so you won't need to insert 1~root~the root node, if you also insert 1~root~the root node>>>2~category1~First category.

You can change this by only creating nodes if the node is the path's last node. The length of a path is count($parts) and you can count which level you are in inside the inner foreach-loop.

I hope this is what you wanted.

like image 123
Basti Avatar answered Sep 28 '22 08:09

Basti