dsfsw1233 2016-08-11 13:43
浏览 118
已采纳

PHP递归菜单多维数组生成器

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!

  • 写回答

1条回答 默认 最新

  • dongtiao5094 2016-08-11 21:49
    关注

    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;
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥40 复杂的限制性的商函数处理
  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码