Creating hierarchy of divs with PHP loop?

Hello,

Im trying to create an employee chain of command in the form of nested divs showing superiority.

I start with a single manager ( id=1 ) and then run 3 of these queries inside each other.

SAMPLE query: SELECT * FROM employees WHERE superior = $superiorID

Obviously this is wrong way to do it. I cant seem to put together a single loop statement that will put all these employees in their respective nested divs in this hierarchy. But i know this is possible.

Hopefully this makes sense and somebody can point me in the right direction. Thanks!!

Not sure if this is what you need.

<?php
$host = "localhost";
//Database user name.	
$login = "";
//Database Password.
$dbpass = "";
//Database name.
$dbname = "";
$PDO = new PDO("mysql:host=localhost;dbname=$dbname", "$login", "$dbpass");

$id = 1;
$employee_names = array();
	
try{
	$sql = "SELECT
	a1.name AS name1,
	a2.name AS name2,
	a3.name AS name3
	
	FROM employees AS a1
	left outer
	 join employees AS a2
	  ON a2.superior = a1.id
	left outer
	 join employees AS a3
	  ON a3.superior = a2.id
	WHERE a1.id = :id";
	$query = $PDO->prepare($sql);
	$query->bindParam(":id", $id);
	$query->execute();
	while($row = $query->fetch(PDO::FETCH_ASSOC)){
		$employee_names[$row['name1']][$row['name2']][] = $row['name3'];
	}

}catch (PDOException $e){
    echo "Database error: ".$e->getMessage();
}


	echo "<pre>";
	print_r($employee_names);
	echo "</pre>";
?>

For my little test it came out like this

Array
(
    [Boss] => Array
        (
            [Manager] => Array
                (
                    [0] => Employee1
                    [1] => Employee2
                )

        )

)

Drummin,

Thanks for that! That would work great, but I want to use a solution that can adapt if there are more levels of command. There will probably be 5 or 6 levels later on.

I already have something working for the current 3 levels. just looking for the completely versatile loop statement.

Just need something that will loop through X amount of times and echo out employees into their bosses div. Divs inside divs basically.

In the end it should look like this (variable amount of employees at each level):


<div class="first">
  <div class="second">
     <div class="third"></div>
     <div class="third"></div>
  </div>
  <div class="second">
     <div class="third"></div>
     <div class="third"></div>
     <div class="third"></div>
     <div class="third"></div>
  </div>
  <div class="second">
     <div class="third"></div>
     <div class="third"></div>
     <div class="third"></div>
  </div>
</div>

<div class="first">
  <div class="second">
     <div class="third"></div>
     <div class="third"></div>
  </div>
  <div class="second">
     <div class="third"></div>
     <div class="third"></div>
     <div class="third"></div>
     <div class="third"></div>
  </div>
  </div>
</div>

Through six levels anyway…

<?php
$host = "localhost";
//Database user name.	
$login = "";
//Database Password.
$dbpass = "";
//Database name.
$dbname = "";
$PDO = new PDO("mysql:host=localhost;dbname=$dbname", "$login", "$dbpass");

$id = 1;
$employee_names = array();
	
try{
	$sql = "SELECT
	a1.name AS name1,
	a2.name AS name2,
	a3.name AS name3,
	a4.name AS name4,
	a5.name AS name5,
	a6.name AS name6
	
	FROM employees AS a1
	left outer
	 join employees AS a2
	  ON a2.superior = a1.id
	left outer
	 join employees AS a3
	  ON a3.superior = a2.id
	left outer
	 join employees AS a4
	  ON a4.superior = a3.id
	left outer
	 join employees AS a5
	  ON a5.superior = a4.id
	left outer
	 join employees AS a6
	  ON a6.superior = a5.id
	WHERE a1.id = :id";
	$query = $PDO->prepare($sql);
	$query->bindParam(":id", $id);
	$query->execute();
	while($row = $query->fetch(PDO::FETCH_ASSOC)){
		$employee_names[$row['name1']][$row['name2']][$row['name3']][$row['name4']][$row['name5']][] = $row['name6'];
	}

}catch (PDOException $e){
    echo "Database error: ".$e->getMessage();
}


	$data = "";
	foreach($employee_names as $k1 => $v1){
		$data .= "<div class=\\"first\\">\\r";
		$data .= "$k1\\r";
		foreach($v1 as $k2 => $v2){
			if(!empty($k2)):
				$data .= "<div class=\\"second\\">\\r";			
				$data .= "$k2\\r";
					foreach($v2 as $k3 => $v3){
						if(!empty($k3)):
							$data .= "<div class=\\"third\\">\\r";			
							$data .= "$k3\\r";
								foreach($v3 as $k4 => $v4){	
									if(!empty($k4)):
										$data .= "<div class=\\"forth\\">\\r";			
										$data .= "$k4\\r";
										foreach($v4 as $k5 => $v5){
											if(!empty($k5)):
												$data .= "<div class=\\"fifth\\">\\r";			
												$data .= "$k5\\r";
												$data .= "</div>\\r";
											endif;
										}
										$data .= "</div>\\r";
									endif;
								}
							$data .= "</div>\\r";
						endif;
					}
				$data .= "</div>\\r";
			endif;
		}
		$data .= "</div>\\r";
	}
	echo $data;
?>

I guess, that display is only five… One more loop using values instead of keys would do it.

If you want to go to an arbitrary level of nestedness you need to use recursion.

<?php
// I can't be bothered to set up a table to make sure this works
// so I'm declaring the data here. This function creates a "record"
function Employee($id, $name, $superiorId) {
   return array('id' => $id, 'name' => $name, 'superiorId' => $superiorId); }

// This is the equivalent of your query function
function subordinatesOf($superiorId) {
   $employees = [ // This is your table
      Employee(1, 'Zim', null),
      Employee(2, 'Gir', 1),
      Employee(3, 'Washing machine', 1),
      Employee(4, 'Dib', null),
      Employee(5, 'Gaz', 4),
      Employee(6, 'Cat', 5) ];
   return array_filter($employees, function($e) use($superiorId) {
      return $e['superiorId'] === $superiorId; }); }

function esc($str) {
   return htmlSpecialChars($str, ENT_QUOTES, 'UTF-8'); }

function intAsEngOrdinal($n) {
   $ordinals = ['zeroth', 'first', 'second', 'third', 'fourth'];
   return $ordinals[$n]; }

// Produces HTML for all subordinates of $superiorId, performing a
// query in the process
function subordinatesHtml($superiorId, $lvl = 1) {
   return implode(array_map(function($sub) use($lvl) {
      return employeeHtml($sub, $lvl); }, subordinatesOf($superiorId))); }

// Produces HTML for a single employee and its subordinates
function employeeHtml($employee, $lvl = 1) {
   return "\
<div class='" . intAsEngOrdinal($lvl) . "'>"
        . '<strong>' . esc($employee['name']) . '</strong>'
        . subordinatesHtml($employee['id'], $lvl + 1)
        . '</div>'; }

echo '<style>div { margin-left: 20px; }</style>';
echo subordinatesHtml(null);

The recursion in that example comes by the fact that subordinatesHtml() calls employeeHtml() that in turn calls subordinatesHtml() again but only if subordinatesOf() returns something other than an empty array, which is important because otherwise it would loop indefinitely.

I’ve used a couple of higher-order functions (array_map() and array_filter()) in there but you could have used loops instead.

Output:

<style>div { margin-left: 20px; }</style>
<div class='first'><strong>Zim</strong>
<div class='second'><strong>Gir</strong></div>
<div class='second'><strong>Washing machine</strong>
<div class='third'><strong>Gaz</strong></div></div></div>
<div class='first'><strong>Dib</strong>
<div class='second'><strong>Cat</strong></div></div>

Bit of continuation…

The major downside with what I initially suggested there is that a query is made for every single item in the hierarchy (tree in computer science terms). The simplest solution to query the whole table and store it all in a PHP array and then you can use the code I have written almost exactly as it is and then there’s only one query :slight_smile:

If you want to go to an arbitrary level of nestedness you need to use recursion.
I’ve just realized I was completely wrong about this. Recursion is by far the simplest way but you can use loops you just have to figure out how each loop needs to be parameterized. I’ll come back with some code in a bit.

My algorithm in this post provides a means of building the hierarchy using a single query. The entire tree is selected than php is used to order the tree using recursion.

Ah, thank you @oddz for that. I didn’t know about the term adjacency list. I will be using that from now on.

OK so here’s the code I promised showing that this can be done without recursion. I’ve actually gone one step further—or perhaps stupider—and done it without using references as well because I wanted to see if it was possible to just using array indexes. I’m happy to say it is.

One of the advantages with the recursion method if you can just put in whatever decoration (bits of strings) you want to appear between the various calls and have it appear within the structure roughly as you would expect. When you use a loop you loose that advantage because the structure of the tree isn’t visually reflected in the code it’s only reflected through the logic you use. As a partial solution to that problem I generalized the loop a bit so that you can supply various parameters that will control how the tree will appear. Unfortunately it is still quite limited: I can think of a number of things you might want to do for which you would have to modify the core loop but the same problem is true of recursion too it’s just that reworking the recursion isn’t so difficult (if you’re used to it).

&lt;?php
function Employee($id, $name, $supId) {
   return array('id' =&gt; $id, 'name' =&gt; $name, 'supId' =&gt; $supId); }

// This is the equivalent of your query function
function subsOf($supNode) {
   $employees = [ // This is your table
      Employee(1, 'Zim', null),
      Employee(2, 'Gir', 1),
      Employee(3, 'Wash', 1),
      Employee(4, 'Dib', null),
      Employee(5, 'Gaz', 4),
      Employee(6, 'Cat', 5) ];
   // `array_values()` required to ensure contiguous seq for `renderNodes()`
   return array_values(array_filter($employees, function($node) use($supNode) {
      return $node['supId'] === $supNode['id']; })); }

function esc($str) {
   return htmlSpecialChars($str, ENT_QUOTES, 'UTF-8'); }

function intAsEngOrdinal($n) {
   $ordinals = ['zeroth', 'first', 'second', 'third', 'fourth'];
   return $ordinals[$n]; }

// Function to render nodes. They can be structured however you like as long as
// you can define a `$getSubsF`. Miniglossary:
//  - subs :: sub nodes
//  - seq :: contiguously indexed array
//  - f :: function
function renderNodes(
   $rootNodes, // seq of one or more root nodes
   $renderRoots, // boolean whether to render the root nodes
   $getSubsF, // returning a list of subs for a node
   $renderNodeF, // returning str from node and depth level (int)
   $postSubsStr, // str to insert after subs
   $betweenSiblingsStr // str to insert between siblings
) {
   $nodeSets = [$rootNodes];
   $xs = [$y = 0];
   $output = '';
   for (;;) {
      $x = $xs[$y];
      if ($nodeSets[$y] === [] || !isSet($nodeSets[$y][$x])) {
         if ($y === 0) { break; }
         if ($y &gt; 0 || $renderRoots) { $output .= $postSubsStr; }
         $y--;
         continue; }
      $node = $nodeSets[$y][$x];
      if ($y &gt; 0 || $renderRoots) {
         if ($x &gt; 0) { $output .= $betweenSiblingsStr; }
         $output .= $renderNodeF($node, $y); }
      $nodeSets[$y + 1] = $getSubsF($node);
      if (isSet($xs[$y])) { $xs[$y]++; }
      else { $xs[$y] = 1; }
      $y++;
      $xs[$y] = 0; }
   return $output; }

// Two examples of usage:

function employeeHtml($e, $lvl) {
   return "\
" . str_repeat(' ', ($lvl - 1) * 2)
        . "&lt;div class='" . intAsEngOrdinal($lvl) . "'&gt;"
        . '&lt;strong&gt;' . esc($e['name']) . '&lt;/strong&gt;'; }

function employeeTxt($e) { return $e['name'] . '['; }

$rootNodes = [Employee(null, '(root)', null)];

echo renderNodes($rootNodes, false, 'subsOf', 'employeeHtml', '&lt;/div&gt;', '');
echo "\
\
";
echo renderNodes($rootNodes, false, 'subsOf', 'employeeTxt', ']', ',');
echo "\
";

Output:


&lt;div class='first'&gt;&lt;strong&gt;Zim&lt;/strong&gt;
  &lt;div class='second'&gt;&lt;strong&gt;Gir&lt;/strong&gt;&lt;/div&gt;
  &lt;div class='second'&gt;&lt;strong&gt;Wash&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class='first'&gt;&lt;strong&gt;Dib&lt;/strong&gt;
  &lt;div class='second'&gt;&lt;strong&gt;Gaz&lt;/strong&gt;
    &lt;div class='third'&gt;&lt;strong&gt;Cat&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

Zim[Gir[],Wash[]],Dib[Gaz[Cat[]]]]

I was going to write a version of this that uses references instead of array indexes but I’ve already spent way too long on this.

Thanks for all the replies, im probably going to use a modified version of what parellelist coded out. Thanks so much for the different perspectives on this issue guys!