Function for iterating out an unordered list of custom taxonomy terms I've created a function that generates an &lt;ul&gt; of my wordpress custom taxonomy terms, displaying parent &gt; child &gt; second child heirachy. Functionally it is working perfectly and replaces my old hard-coded menu. 

The problem is I'm inexperienced and recognise it as an ugly, inefficient function but could not figure out how to make it better. I tried use 'While()' loops to simplify things but came unstuck. Could someone help me understand how to improve this. The final output (including CSS and jQuery to collapse deactivated menu items) is included below. 

//Display Navigation Menu

function taxonomy_nav(){

  //Get Parent Terms
  $getParents = [
    'taxonomy'     =&gt; 'kernal_category',
    'parent'        =&gt; 0,
    'number'        =&gt; 0,
    'hide_empty'    =&gt; false  
  ];

  //Assign parent terms to variable
  $parent_terms = get_terms( $getParents );  

  //Loop through parent terms
  foreach ( $parent_terms as $parent_term ) {   

    //Store children terms 
    $childrenCheck = get_term_children( $parent_term-&gt;term_id, $parent_term-&gt;taxonomy );

    //If parent term has children...
    if ( ! is_wp_error( $childrenCheck ) &amp;&amp; ! empty ( $childrenCheck ) ) {

      //Output parent term and start new children list
      echo '&lt;li&gt;&lt;a href="'. get_term_link( $parent_term ) .'"&gt;'. $parent_term-&gt;name.'&lt;/a&gt;';
      echo '&lt;ul&gt;';

        //Loop through child items and output name
        foreach ( get_terms( 'kernal_category', array( 'hide_empty' =&gt; false, 'parent' =&gt; $parent_term-&gt;term_id ) ) as $child_term) {

            //Check if children have children
            $secondChildrenCheck = get_term_children( $child_term-&gt;term_id, $child_term-&gt;taxonomy );

              //Generate list for second children
              if ( ! is_wp_error( $secondChildrenCheck ) &amp;&amp; ! empty( $secondChildrenCheck ) ) {

                echo '&lt;li&gt;&lt;a href="' . get_term_link( $child_term ) . '"&gt;' . $child_term-&gt;name.'&lt;/a&gt;';
                echo '&lt;ul&gt;';

                //If child has child, look through child
                foreach ( get_terms( 'kernal_category', array( 'hide_empty' =&gt; false, 'parent' =&gt; $child_term-&gt;term_id ) ) as $second_child_term) {

                 echo '&lt;li&gt;&lt;a href="'. get_term_link( $second_child_term ) . '"&gt;'.$second_child_term-&gt;name . '&lt;/a&gt;&lt;/li&gt;';       

                }

                echo '&lt;/ul&gt;';
                echo '&lt;/li&gt;';       

              } else {

              echo '&lt;li&gt;&lt;a href="'. get_term_link( $child_term ) .'"&gt;'. $child_term-&gt;name.'&lt;/a&gt;&lt;/li&gt;';

              }
        }

      echo '&lt;/ul&gt;';
      echo '&lt;/li&gt;';

    } else {

      echo '&lt;li&gt;&lt;a href="'. get_term_link( $parent_term ) .'"&gt;'. $parent_term-&gt;name.'&lt;/a&gt;&lt;/li&gt;';

    }     

  }
As pointed out, there is a lot of code repetition and this is a big problem when any changes get made.  It's so easy to quickly make a change to one piece of code and miss the 3 other versions of it which do the same thing.

Rather than factor out these parts of code into functions though, you can re-factor the code into a recursive function.  The idea being that you just use the same code to do each layer of the process rather than loops within loops calling helper functions to do the more common tasks.

One other thing I have changed is to build the output into a string rather than echo it out directly.  I would normally then return this string to the calling code and let that do whatever it wants with it.  This allows more control over the output generated, also allows the output to be re-used (although not sure how that would be necessary in this case) rather than having to re-generate the output each time.  But as I'm not sure how this is called, I just echo the combined string at the end.

function taxonomy_nav( string $category = null, array $settings = null  ) : string {
    if ( $category == null )    {
        $settings = [
            'taxonomy'     =&gt; 'kernal_category',
            'parent'        =&gt; 0,
            'number'        =&gt; 0,
            'hide_empty'    =&gt; false
        ];
        $parent_terms = get_terms( $settings );
    }
    else    {
        $parent_terms = get_terms( $category, $settings );
    }

    foreach ( $parent_terms as $parent_term ) {
        $list =  '&lt;li&gt;&lt;a href="'. get_term_link( $parent_term ) .'"&gt;'. $parent_term-&gt;name.'&lt;/a&gt;';

        $childrenCheck = get_term_children( $parent_term-&gt;term_id, $parent_term-&gt;taxonomy );

        if ( ! is_wp_error( $childrenCheck ) &amp;&amp; ! empty ( $childrenCheck ) ) {
            $list .= '&lt;ul&gt;'
                . taxonomy_nav ( 'kernal_category', array( 'hide_empty' =&gt; false, 'parent' =&gt; $parent_term-&gt;term_id )  ) 
                . '&lt;/ul&gt;';
        }

        $list .= '&lt;/li&gt;';
    }

    echo $list;
}


As you can see, in the if ( ! is_wp_error( $childrenCheck ) block, it just starts the sub list and then calls itself to do the processing for any child nodes.  

This also shows how the list item itself is only ever generated by the one line of code, not repetition and any changes are easily identified.