简体   繁体   中英

PHP recursive menu multi-dimensional array generator

I'm struggling a bit with some of my code...I have an array of my menu layout:

$navigationMenu = [
    SECTION_OVERVIEW => [
        'hide' => TRUE,
        'id' => 'overview_button',
        'order' => 0,
        'content/overview.php' => [

            SECTION_CABINET => [
                'hide' => TRUE,
                'id' => 'cabinet_button',
                'content/cabinet.php' => [

                    SECTION_AMP_PSU => [
                        'hide' => TRUE,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => [

                            SECTION_AMPS_OFF => [
                                'hide' => false,
                                'id' => 'amps_off_button',
                                'ampNoIndicator' => TRUE,
                                'class' => 'red',
                                'order' => 4,
                            ],

                            SECTION_AMPS_ALARMS => [
                                'id' => 'amps_alarms',
                                'ampNoIndicator' => TRUE,
                                'order' => 5,
                                'content/amp_alarms.php' => NULL
                            ]
                        ]
                    ],
                    'POOP' => [
                        'hide' => false,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => NULL
                    ]
                ]
            ]
        ]
    ],
    SECTION_SYSCONFIG => [
        'id' => 'sysConfig',
        'order' => 1,
        'content/system_configuration.php' => NULL,
    ],

    SECTION_SYS => [
        'id' => 'system',
        'order' => 2,
        'content/system.php' => NULL,
    ],

    BACK_BUTTON => [
        'id' => 'backBtn',
        'order' => 100,
        'prev' => NULL,
    ],

    RESCAN_BUTTON => [
        'id' => 'rescan',
        'order' => 101,
    ]
];

And I have a class that generates the menu.

class Navigation {
    private $doc;
    private $xpath;
    private $rootNode;

    /**
     *  Initialize navigation.
     */
    public function __construct() {
        global $navigationMenu;
        $menu = $navigationMenu;
        $this->doc = new DOMDocument();
        $this->doc->appendChild($this->buildMenu($menu));
        $this->xpath = new DOMXpath($this->doc);
    }

    /**
     *  Build Menu
     *  @param  $menu       array       see $menu array at the begining of the class
     *  @param  $depth      int         maintain a depth count for nested UL menu
     *  @param  $nestingUL  bool        false (default): does not output menu in a nested UL config
     *                                  true: nested UL config
     *  
     *  @return DOMElement              function returns DOM element of UL
     */
    private function buildMenu($menu, $depth = 0, $nestingUL = false) {
        $ulNode = $this->doc->createElement('ul');

        if (!$depth) {
            $ulNode->setAttribute('id', 'primary-nav');
            $this->rootNode = $ulNode;
        }

        $this->appendtoNode($menu, $depth, $nestingUL, $ulNode);

        return $ulNode;
    }

    /**
     *  Append menu items to a node (either UL or LI)
     *  
     *  @param  $menu                   array       array of menu items list
     *  @param  $depth                  array       depth count for nested UL menu
     *  @param  $nestingUL              bool        false: no nesting UL; true: nesting UL
     *  @param  $node                   DOMElement  passing node variable
     *  @param  $showElementOverride    bool        override skipping hidden elements
     *  
     */
    private function appendtoNode($menu, $depth, $nestingUL, $node, $showElementOverride = false) {
        foreach ($menu as $itemText => $item) {
            if ((empty($item['hide']) || !($item['hide']) || $showElementOverride)) {
                $node->appendChild($this->buildMenuItem($itemText, $item, $depth, $nestingUL));
            }

            else if (array_key_exists('hide', $item) && $item['hide']) {
                $newArr = $this->hiddenArraySkipNext($menu);
                $this->appendtoNode($newArr, $depth, $nestingUL, $node, $showElementOverride);
            }
        }
        return;
    }

  /**
   *  Build menu item.
   *  
   *  @param   $itemText        string          (button/menu label)
   *  @param   $item            array           (button modifiers array)
   *  @param   $depth           int             (maintaining depth count (only for creating a nested UL))
   *  @param   $nesting         bool            (true if nesting is required; false if no nesting)
   */  
    private function buildMenuItem($itemText, $item, $depth, $nesting) {
        $id = '';
        $spanclassAttr = array('navButton-color');
        $order = '';
        $url = '';
        $ampNo = '';
        $childMenu = false;

        // prepare node structure
           $liNode = $this->doc->createElement('li');
         $spanNode = $this->doc->createElement('span');
        $glareNode = $this->doc->createElement('span');     // spare span tag used to carry the glare class attribute
            $pNode = $this->doc->createElement('p');

        // initialize node attributes
        $liNode->setAttribute('class', 'navButton');
        $glareNode->setAttribute('class', 'glare');         // spare span tag with "glare" class attribute

        // iterate item properties
        foreach ($item as $key => $value) {
            switch ($key) {
                case 'hide':
                    break;
                case 'id':
                    $id = $value;
                    break;
                case 'ampNoIndicator':
                    $ampNo = $value;
                    break;
                case 'class':
                    $spanclassAttr[] = $value;
                    break;
                case 'order':
                    $order = $value;
                    break;
                default:
                    $url = $key;
                    $childMenu = $value;
            }
        }

        // map iterated items to nodes
        $liNode->setAttribute('id', $id);

        if ($spanclassAttr) {
            $spanNode->setAttribute('class', implode(' ', $spanclassAttr));
        }

        $pNode->nodeValue = $itemText;
        $spanNode->appendChild($pNode);
        $liNode->appendChild($spanNode);
        $liNode->appendChild($glareNode);

        if (is_array($childMenu) && $nesting) {
            $liNode->appendChild($this->buildMenu($childMenu, $depth + 1, $nesting));
        }
        else if (is_array($childMenu) && !$nesting) {
            $this->appendtoNode($childMenu, $depth, $nesting, $liNode);
        }
        return $liNode;
    }

    /**
     *  Iterating menu array
     *  
     *  @param  $item           menu array
     *  @return array | bool    return the nested array, else return false
     */
    private function hiddenArraySkipNext($arr) {
        $output = $arr;
        foreach ($arr as $tempArr) {
            if (array_key_exists('hide', $tempArr) && $tempArr['hide']) {
                foreach ($tempArr as $key => $value) {
                    if (is_array($value)) {
                        $output = $value;
                        $this->hiddenArraySkipNext($output);
                    }
                }
            }
            else if (!array_key_exists('hide', $tempArr) || (array_key_exists('hide', $tempArr) && !$tempArr['hide'])) {
                return $output;
            }
        }
        return $output;
    }

    /**
    * Get menu.
    */
    public function getMenu() {
        return $this->doc->saveHTML();
    }
}

So the purpose of the class is to be able to generate either a nested or non-nested UL of the $navigationMenu (see the use of the $nestingUL/$nesting parameters I have through buildMenu, appendtoNode, etc). I also want to have the ability to override any "hide" keys in my $navigationMenu (see $showElementOverride in the appendtoNode() function). The menu displays as planned when the array is in the configuration I have set up. However, if I ever wanted to expand on the menu, I run into issues.

In this array configuration, below, my SECTION_AMPS_OFF and SECTION_AMPS_ALARMS are output twice.

$navigationMenu = [
    SECTION_OVERVIEW => [
        'hide' => TRUE,
        'id' => 'overview_button',
        'order' => 0,
        'content/overview.php' => [

            SECTION_CABINET => [
                'hide' => TRUE,
                'id' => 'cabinet_button',
                'content/cabinet.php' => [

                    SECTION_AMP_PSU => [
                        'hide' => TRUE,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => [

                            SECTION_AMPS_OFF => [
                                'hide' => false,
                                'id' => 'amps_off_button',
                                'ampNoIndicator' => TRUE,
                                'class' => 'red',
                                'order' => 4,
                            ],

                            SECTION_AMPS_ALARMS => [
                                'id' => 'amps_alarms',
                                'ampNoIndicator' => TRUE,
                                'order' => 5,
                                'content/amp_alarms.php' => NULL
                            ]
                        ]
                    ],
                    'POOP' => [
                        'hide' => true,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => NULL
                    ]
                ]
            ]
        ]
    ],
    SECTION_SYSCONFIG => [
        'id' => 'sysConfig',
        'order' => 1,
        'content/system_configuration.php' => NULL,
    ],

    SECTION_SYS => [
        'id' => 'system',
        'order' => 2,
        'content/system.php' => NULL,
    ],

    BACK_BUTTON => [
        'id' => 'backBtn',
        'order' => 100,
        'prev' => NULL,
    ],

    RESCAN_BUTTON => [
        'id' => 'rescan',
        'order' => 101,
    ]
];

And when I have the menu configured as below, I get a fatal error about the allowed memory size exhausted.

$navigationMenu = [
    SECTION_OVERVIEW => [
        'hide' => TRUE,
        'id' => 'overview_button',
        'order' => 0,
        'content/overview.php' => [

            SECTION_CABINET => [
                'hide' => TRUE,
                'id' => 'cabinet_button',
                'content/cabinet.php' => [

                    SECTION_AMP_PSU => [
                        'hide' => TRUE,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => [

                            SECTION_AMPS_OFF => [
                                'hide' => true,
                                'id' => 'amps_off_button',
                                'ampNoIndicator' => TRUE,
                                'class' => 'red',
                                'order' => 4,
                            ],

                            SECTION_AMPS_ALARMS => [
                                'id' => 'amps_alarms',
                                'ampNoIndicator' => TRUE,
                                'order' => 5,
                                'content/amp_alarms.php' => NULL
                            ]
                        ]
                    ],
                    'POOP' => [
                        'hide' => false,
                        'id' => 'amps_psu_button',
                        'ampNoIndicator' => TRUE,
                        'order' => 3,
                        'content/amp_psu.php' => NULL
                    ]
                ]
            ]
        ]
    ],
    SECTION_SYSCONFIG => [
        'id' => 'sysConfig',
        'order' => 1,
        'content/system_configuration.php' => NULL,
    ],

    SECTION_SYS => [
        'id' => 'system',
        'order' => 2,
        'content/system.php' => NULL,
    ],

    BACK_BUTTON => [
        'id' => 'backBtn',
        'order' => 100,
        'prev' => NULL,
    ],

    RESCAN_BUTTON => [
        'id' => 'rescan',
        'order' => 101,
    ]
];

I'm pretty sure the solution is not expanding the memory limit. The ultimate goal is to be able to loop through the array and spit out any unhidden nav menu buttons (either nested or non-nested UL), as long as $showElementOverride is not set TRUE. If a parent array is hidden, the child may be shown, as long as it's not a nested UL. I haven't tackled that issue yet, as I've been trying to tackle the hiddenArraySkipNext() function. I've been struggling with this for the past couple of days, so a fresh set of eyes would definitely be appreciated!

Rewrote my buildMenu(), appendtoNode() and hiddenArraySkipNext() functions:

private function buildMenu($menu, $depth = 0, $nestingUL = true, $showElementOverride = false) {
    $ulNode = $this->doc->createElement('ul');

    if (!$depth) {
        $ulNode->setAttribute('id', 'primary-nav');
        $this->rootNode = $ulNode;
    }

    $this->appendtoNode($menu, $depth, $nestingUL, $ulNode, $showElementOverride);

    return $ulNode;
}

private function appendtoNode($menu, $depth, $nestingUL, $node, $showElementOverride = false) {
    foreach ($menu as $itemText => $item) {
        if (empty($item['hide']) || !($item['hide']) || $showElementOverride) {
            $node->appendChild($this->buildMenuItem($itemText, $item, $depth, $nestingUL));
        }

        else if ((array_key_exists('hide', $item) && $item['hide']) && !$nestingUL) {
            $tempMenu = $this->hiddenArraySkipNext($item);

            if (is_array($tempMenu)) $this->appendtoNode($tempMenu, $depth, $nestingUL, $node, $showElementOverride);
        }
    }
    return;
}

private function hiddenArraySkipNext($arr = array()) {
    $output = array();
    foreach ($arr as $tempArr) {
        $output = $tempArr;
        if (is_array($tempArr) && !empty($tempArr)) {
            foreach ($tempArr as $val) {
                if (is_array($val) && array_key_exists('hide', $val) && $val['hide']) {
                    $this->hiddenArraySkipNext($val);   
                }
            }
        }
    }
    return $output;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM