Asked  7 Months ago    Answers:  5   Viewed   32 times

I have an array in PHP, which looks like this:

array (
    [0] => array (
        [id] => 1
        [title] => "Title 1"
        [parent_id] => NULL
        [depth] => 0
    )
    [1] => array (
        [id] => 2
        [title] => "Title 2"
        [parent_id] => NULL
        [depth] => 0
    )
    [2] => array (
        [id] => 3
        [title] => "Title 3"
        [parent_id] => 2
        [depth] => 1
    )
    [3] => array (
        [id] => 4
        [title] => "Title 4"
        [parent_id] => 2
        [depth] => 1
    )
    [4] => array (
        [id] => 5
        [title] => "Title 5"
        [parent_id] => NULL
        [depth] => 0
    )
    [5] => array (
        [id] => 6
        [title] => "Title 6"
        [parent_id] => 4
        [depth] => 2
    )
)

What i want to do is iterate over this array and create a nested <ol> list from it. So the result should look like this:

<ol>
    <li>Title 1</li> // id = 1
    <li>Title 2</li> // id = 2
    <ol>
        <li>Title 3</li> // id = 3 -> parent_id = 2
        <li>Title 4</li> // id = 4 -> parent_id = 2
        <ol>
            <li>Title 6</li> // id = 6 -> parent_id = 4
        </ol>
    </ol>
    <li>Title 5</li> // id = 5
</ol>

I've been trying to think of a way how i could get this done. But so far every attempt failed...

Anyone any idea how i can create such a nested <ol> list from an array like that?

Please note that i do not have any control on the given data. I simply make a call to an API and it returns json data, which i convert to an array. And the array looks exactly like the one i described.

 Answers

69

You should use recursion:

First the array in 'php' syntax:

<?php
$a=array (
    '0' => array (
        'id' => 1,
        'title' => "Title 1",
        'parent_id' => 'NULL',
        'depth' => 0
    ),
    '1' => array (
        'id' => 2,
        'title' => "Title 2",
        'parent_id' => 'NULL',
        'depth' => 0
    ),
    '2' => array (
        'id' => 3,
        'title' => "Title 3",
        'parent_id' => 2,
        'depth' => 1
    ),
    '3' => array (
        'id' => 4,
        'title' => "Title 4",
        'parent_id' => 2,
        'depth' => 1
    ),
    '4' => array (
        'id' => 5,
        'title' => "Title 5",
        'parent_id' => 'NULL',
        'depth' => 0
    ),
    '5' => array (
        'id' => 6,
        'title' => "Title 6",
        'parent_id' => 4,
        'depth' => 0
    )
);

Here the code:

$level = 'NULL';

function r( $a, $level) {
   $r = "<ol>";
   foreach ( $a as $i ) {
       if ($i['parent_id'] == $level ) {
          $r = $r . "<li>" . $i['title'] . r( $a, $i['id'] ) . "</li>";
       }
   }
   $r = $r . "</ol>";
   return $r;
}

print r( $a, $level );

?>

The results:

<ol><li>Title 1<ol></ol></li><li>Title 2<ol><li>Title 3<ol>
</ol></li><li>Title 4<ol><li>Title 6<ol></ol></li></ol></li></ol></li><li>Title 5
<ol></ol></li></ol>
  1. Title 1n
    1. Title 2n
      1. Title 3n
        1. Title 4n
          1. Title 6n
        2. Title 5n

          EDITED AFTER CHECK AS SOLUTION

          To avoid empty leafs:

          function r( $a, $level) {
             $r = '' ;
             foreach ( $a as $i ) {
                 if ($i['parent_id'] == $level ) {
                    $r = $r . "<li>" . $i['title'] . r( $a, $i['id'] ) . "</li>";
                 }
             }
             return ($r==''?'':"<ol>". $r . "</ol>");
          }
          
          Wednesday, March 31, 2021
           
          lechup
          answered 7 Months ago
          39

          When you set cell values individually, you have the option of setting the datatype explicitly, but when you use the fromArray() method, you don't have this option.

          However, by default, PHP uses a default value binder to identify datatypes from the values passed, and set the cell datatype accordingly. This default behaviour is defined in a class /PHPExcel/Cell/DefaultValueBinder.php.

          So you can create your own value binder, as described in the PHPExcel Documentation, that would set every value as a string datatype.

          Something like:

          class PHPExcel_Cell_MyColumnValueBinder extends PHPExcel_Cell_DefaultValueBinder implements PHPExcel_Cell_IValueBinder
          {
              protected $stringColumns = [];
          
              public function __construct(array $stringColumnList = []) {
                  // Accept a list of columns that will always be set as strings
                  $this->stringColumns = $stringColumnList;
              }
          
              public function bindValue(PHPExcel_Cell $cell, $value = null)
              {
                  // If the cell is one of our columns to set as a string...
                  if (in_array($cell->getColumn(), $this->stringColumns)) {
                      // ... then we cast it to a string and explicitly set it as a string
                      $cell->setValueExplicit((string) $value, PHPExcel_Cell_DataType::TYPE_STRING);
                      return true;
                  }
                  // Otherwise, use the default behaviour
                  return parent::bindValue($cell, $value);
              }
          }
          
          // Instantiate our custom binder, with a list of columns, and tell PHPExcel to use it
          PHPExcel_Cell::setValueBinder(new PHPExcel_Cell_MyColumnValueBinder(['A', 'B', 'C', 'E', 'F']));
          
          $objPHPExcel = new PHPExcel();
          $objPHPExcel->getActiveSheet()->fromArray($dataArray,null,"A2");
          
          Friday, May 28, 2021
           
          Wilk
          answered 5 Months ago
          13

          There's a lot been written about PHPExcel and memory use, and I'm not going to repeat it all here.

          Try reading some of the threads on the PHPExcel discussion board discussing the issue, such as this one; or previous answers here on SO such as this one or this one

          Tuesday, July 6, 2021
           
          Slinky
          answered 4 Months ago
          28

          Try this;

          function buildTree(Array $data, $parent = 0) {
              $tree = array();
              foreach ($data as $d) {
                  if ($d['parent'] == $parent) {
                      $children = buildTree($data, $d['id']);
                      // set a trivial key
                      if (!empty($children)) {
                          $d['_children'] = $children;
                      }
                      $tree[] = $d;
                  }
              }
              return $tree;
          }
          
          
          $rows = array(
              array ('id' => 1, 'name' => 'Test 1', 'parent' => 0),
              array ('id' => 2, 'name' => 'Test 1.1', 'parent' => 1),
              array ('id' => 3, 'name' => 'Test 1.2', 'parent' => 1),
              array ('id' => 4, 'name' => 'Test 1.2.1', 'parent' => 3),
              array ('id' => 5, 'name' => 'Test 1.2.2', 'parent' => 3),
              array ('id' => 6, 'name' => 'Test 1.2.2.1', 'parent' => 5),
              array ('id' => 7, 'name' => 'Test 2', 'parent' => 0),
              array ('id' => 8, 'name' => 'Test 2.1', 'parent' => 7),
          );
          
          $tree = buildTree($rows);
          // print_r($tree);
          
          function printTree($tree, $r = 0, $p = null) {
              foreach ($tree as $i => $t) {
                  $dash = ($t['parent'] == 0) ? '' : str_repeat('-', $r) .' ';
                  printf("t<option value='%d'>%s%s</option>n", $t['id'], $dash, $t['name']);
                  if ($t['parent'] == $p) {
                      // reset $r
                      $r = 0;
                  }
                  if (isset($t['_children'])) {
                      printTree($t['_children'], ++$r, $t['parent']);
                  }
              }
          }
          
          
          print("<select>n");
          printTree($tree);
          print("</select>");
          

          Output;

          <select>
              <option value='1'>Test 1</option>
              <option value='2'>- Test 1.1</option>
              <option value='3'>- Test 1.2</option>
              <option value='4'>-- Test 1.2.1</option>
              <option value='5'>-- Test 1.2.2</option>
              <option value='6'>--- Test 1.2.2.1</option>
              <option value='7'>Test 2</option>
              <option value='8'>- Test 2.1</option>
          </select>
          

          And in your case;

          <select>
              <option value='1'>Baden-Württemberg</option>
              <option value='2'>- DMP-Verträge</option>
              <option value='50'>- Sprechstundenbedarf</option>
              <option value='52'>- Richtgrößen</option>
              <option value='53'>- Prüfungen</option>
              <option value='54'>- DMP-Verträge</option>
              <option value='55'>- Sonstige Verträge</option>
              <option value='3'>Berlin</option>
              <option value='62'>- DMP-Verträge</option>
              <option value='63'>- Prüfungen</option>
              <option value='64'>- Richtgrößen</option>
              <option value='65'>- Sonstige Verträge</option>
              <option value='66'>- Sprechstundenbedarf</option>
              <option value='4'>Brandenburg</option>
              <option value='67'>- DMP-Verträge</option>
              <option value='68'>- Prüfungen</option>
              <option value='69'>- Richtgrößen</option>
              <option value='70'>- Sonstige Verträge</option>
              <option value='71'>- Sprechstundenbedarf</option>
              <option value='5'>Bremen</option>
              <option value='72'>- DMP-Verträge</option>
              <option value='73'>- Prüfungen</option>
              <option value='74'>- Richtgrößen</option>
              <option value='75'>- Sonstige Verträge</option>
              <option value='76'>- Sprechstundenbedarf</option>
              <option value='7'>Hessen</option>
              <option value='6'>Hamburg</option>
              <option value='8'>Mecklenburg-Vorpommern</option>
              <option value='9'>Niedersachsen</option>
              <option value='10'>Nordrhein</option>
              <option value='11'>Rheinland-Pfalz</option>
              <option value='12'>Saarland</option>
              <option value='13'>Sachsen</option>
              <option value='14'>Sachsen-Anhalt</option>
              <option value='15'>Schleswig-Holstein</option>
              <option value='16'>Thüringen</option>
              <option value='17'>Westfalen-Lippe</option>
              <option value='51'>Richtgrössen</option>
              <option value='56'>Bayern</option>
              <option value='57'>- DMP-Verträge</option>
              <option value='58'>- Prüfungen</option>
              <option value='59'>- Richtgrößen</option>
              <option value='60'>- Sonstige Verträge</option>
              <option value='61'>- Sprechstundenbedarf</option>
          </select>
          
          Monday, August 2, 2021
           
          o_flyer
          answered 3 Months ago
          74

          This is the way that someone suggested to solved for String type. Cast2(List<?>) returns the multidimensional array. It may be generalized to use the class type as parameter. Thank you for your comments.

          static int dimension2(Object object) {
          
              int result = 0;
              if (object instanceof List<?>) {
          
                  result++;
                  List<?> list = (List<?>) object;
                  for (Object element : list) {
                      if (element != null) {
                          result += dimension2(element);
                          break;
                      }
                  }
              }
          
              return result;
          }
          
          
          static Object cast2(List<?> l) {
          
              int dim = dimension2(l);
              if (dim == 1) {
                  return l.toArray(new String[0]);
              }
          
              int[] dims = new int[dimension2(l)];
              dims[0] = l.size();
              Object a = Array.newInstance(String.class, dims);
              for (int i = 0; i < l.size(); i++) {
          
                  List<?> e = (List<?>) l.get(i);
                  if (e == null) {
                      Array.set(a, i, null);
                  } else if (dimension2(e) > 1) {
                      Array.set(a, i, cast2(e));
                  } else {
                      Array.set(a, i, e.toArray(new String[0]));
                  }
              }
              return a;
          }
          
          Monday, October 25, 2021
           
          Assassin
          answered 3 Days ago
          Only authorized users can answer the question. Please sign in first, or register a free account.
          Not the answer you're looking for? Browse other questions tagged :