I am trying to create a multi-level list of HTML from a source array that is formatted as follows:
$list = array( array( 'id' => 1, 'parent_id' => 0, 'text' => 'Level 1', ), array( 'id' => 2, 'parent_id' => 0, 'text' => 'Level 2', ), array( 'id' => 3, 'parent_id' => 2, 'text' => 'Level 2.1', ), array( 'id' => 4, 'parent_id' => 2, 'text' => 'Level 2.2', ), array( 'id' => 5, 'parent_id' => 4, 'text' => 'Level 2.2.1', ), array( 'id' => 6, 'parent_id' => 0, 'text' => 'Level 3', ) );
The goal is a nested <ul> with infinite depth. Expected array output above:
If only the elements of the array had a key named child or something that contained the actual submatrix, it would be easy to recursively occupy them and get the desired result using this function:
function makeList($list) { echo '<ul>'; foreach ($list as $item) { echo '<li>'.$item['text']; if (isset($item['child'])) { makeList($item['child']); } echo '</li>'; } echo '</ul>'; }
Unfortunately, not for me - the format of the source arrays cannot be changed. So, a long time ago I wrote this very unpleasant function to make this happen, and it works only up to three levels (the code is inserted verbatim with the original comments). I know this is a long boring read, please carry me:
function makeArray($links) { // Output $nav = array(); foreach ($links as $k => $v) { // If no parent_id is present, we can assume it is a top-level link if (empty($v['parent_id'])) { $id = isset($v['id']) ? $v['id'] : $k; $nav[$id] = $v; // Remove from original array unset($links[$k]); } } // Loop through the remaining links again, // we can assume they all have a parent_id foreach ($links as $k => $v) { // Link parent_id is in the top level array, so this is a level-2 link // We already looped through every item so we know they are all accounted for if (isset($nav[$v['parent_id']])) { $id = isset($v['id']) ? $v['id'] : $k; // Add it to the top level links as a child $nav[$v['parent_id']]['child'][$id] = $v; // Set a marker so we know which ones to loop through to add the third level $nav2[$id] = $v; // Remove it from the array unset($links[$k]); } } // Last iteration for the third level // All other links have been removed from the original array at this point foreach ($links as $k => $v) { $id = isset($v['id']) ? $v['id'] : $k; // Link parent_id is in the second level array, so this is a level-3 link // Orphans will be ignored if (isset($nav2[$v['parent_id']])) { // This part is crazy, just go with it $nav3 = $nav2[$v['parent_id']]['parent_id']; $nav[$nav3]['child'][$v['parent_id']]['child'][] = $v; } } return $nav; }
This makes an array like:
array( 'text' => 'Level 1' 'child' => array( array( 'text' => 'Level 1.2' 'child' => array( array( 'text' => 'Level 1.2.1' 'child' => array(
Using:
$nav = makeArray($links); makeList($nav);
I spent a lot of free hours trying to figure it out, and the source code I provided here is still the best solution I could create.
How can I do this without this terrible function (which is limited to a depth of 3) and has an infinite number of levels? Is there a more elegant solution for this?