构建一个用于获取菜单树的数组- PHP


Build an Array used to get a menu tree - PHP

我一直在寻找这个问题的解决方案&我只找到了直接显示菜单的解决方案,因为我需要后端菜单结构用于编辑目的,并且要学习最有效的编程方式,我不想直接打印菜单,而是用树结构填充数组。

我的目标是得到这样的东西:

$items[$itemId] = new ItemObject;
$items[$itemId][$childItemId] = new ItemObject;
$items[$itemId] = new ItemObject;
$items[$itemId][$childItemId] = new ItemObject;
$items[$itemId][$childItemId][$childItemId] = new ItemObject;

数据存储在mysql数据库中,并且没有排序,因为可以随时更改父条目。

数据库的例子:

MenuID    |    MenuName    |    MenuParentId
1         |    Paintings   |    0
2         |    Cars        |    3
3         |    Vehicles    |    0
4         |    Boats       |    3
5         |    Audi        |    2
6         |    Honda       |    2
7         |    A3          |    5
8         |    TDI         |    7

我做的工作与对象,我确实想得到一个数组填充如前所述,这是没有问题的,如果我只有1个子级别,或者如果我知道有多少个子级别是给定的,但情况并非如此,因为一个人应该能够添加尽可能多的子级别作为一个人的愿望。

我不会通过它们的id来引用它们,因为这使得在阅读代码时很难确切地知道菜单项是做什么的。我会使用PHP的关联数组,并给它们一个显式的键。你必须确保每个键都是唯一的,但你也可以让唯一键不同于显示的键,所以如果你有两个名为honda的不同菜单项,一个是honda_cars,另一个是honda_streetfighter,但它们的值都是'honda'。

示例:您可以看到$items['cars']['audi']['a3']['tdi'],而不是看到$items[2][5][7][8],这样更易于阅读。

我最近研究了一个类似的问题,我们寻找了一些好的解决方案,因为我们有很多子级别和很多数据。我建议你去看看嵌套集。

我不会在这里向你解释,你可以在网上找到一些更好的方法。

优势:

  • 更快地搜索到树
  • 可以轻松处理无限级别

缺点:

  • 插入/更新/删除的速度较慢
  • 比邻接表模型更难理解

我猜还有更多,但这些是我使用后能分辨出来的。

如果你想了解更多关于管理分层数据的信息,你可以去这里看看,它提供了一些关于在MySQL中使用这两种解决方案的好信息(所有归功于它的作者)。

希望它有帮助,如果你找到其他的方法,让我们知道!

像你发布的表结构可以用递归算法建立一个层次结构,如下所示:

//This class represents a menu object which can have several children (sub menu levels)
//and can be a sub menu by itself
class ItemObject {
    public $id, $name, $children = array();
    //Creates a new item object and if there is an sql result array given, the method 
    //parseChildren is called directly
    public function __construct($id, $name, array $sqlArray = null) {
        $this->id = $id;
        $this->name = $name;
        if($sqlArray !== null) {
            $this->parseChildren($sqlArray);
        }
    }
    //This function iterates through the given array and creates new child nodes if
    //the MenuParentID for the current row matches the id of the current ItemObject.
    //Because this method will be called from the constructor and also creates new 
    //item objects which also causes the constructor of the child object to be exe-
    //cuted, this is a recursive algorithm
    public function parseChildren(array $results) {
        foreach($results as $value) {
            if($this->id === (int)$value['MenuParentID']) {
                $this->children[] = new ItemObject((int)$value['MenuID'],
                  $value['MenuName'], $results);
            }
        }
    }
}
//This class is used for the top level (ID 0) with a special constructor.
class ItemObjectContainer extends ItemObject {
    public function __construct(array $sqlArray) {
        $this->id = 0;
        $this->parseChildren($sqlArray);
    }
}
//This function creates an array from an Mysqli result object, can be altered
//if for example PDO is used
function toArray(mysqli_result $result) {
    return $result->fetch_all(MYSQLI_ASSOC);
}
//This function finally creates an array of ItemObjects with their correct hierarchy
function createMenu(mysqli_result $result) {
    $resultArray = toArray($result);
    $menuObject = new ItemObjectContainer($resultArray);
}

我自己更喜欢递归方法,但一个人必须决定是否要使用它们。ItemObject和ItemObjectContainer类的扩展可以是一个递归方法,它将导航打印到web浏览器。

这里有一些链接可能会对你有所帮助:

  • 在数据库中存储分层数据
  • 管理MySQL中的分层数据
在这两篇文章中都解释了两种最常见的方法,邻接表模型(您正在使用的模型)和嵌套集模型。对于较大的数据集,我将推荐嵌套模型,而对于较小的邻接模型将足够快。

我猜你想要创建对象的树形结构,并用数据库中的数据填充它们。PHP已经实现了帮助您这样做所需的一切。第一个有用的方法是mysqli_result::fetch_object。它将返回结果集的行作为对象,但这里更重要的是,您可以实例化自己的对象,该对象将填充来自结果集的数据行。这个例子将递归地创建MenuItem对象,每个对象将保存来自数据库的数据,并将拥有一个子项目数组(如果存在的话)。如果您计划使用MenuHrefMenuDescription这样的字段扩展数据库,那么这些属性也将添加到对象中。MenuItem对象结构示例:

<>之前菜单项对象([MenuID] => 0[children] => Array ([0] => MenuItem[MenuID] => 1[MenuName] =>绘画[MenuParentId] => 0)[3] => MenuItem Object[MenuID] => 3[MenuName] =>车辆[MenuParentId] => 0[children] => Array ([2] => MenuItem Object[MenuID] => 2[MenuName] => Cars[MenuParentId] => 3[children] =>数组(…))[4] => MenuItem Object[MenuID] => 4[MenuName] =>船[MenuParentId] => 3)))))之前

在某些时候,您可能想要打印菜单。PHP有神奇的方法__toString(),这是完美的。当对象被当作字符串处理时,__toString()方法被启动。由于所有对象都有这个方法,PHP将递归地收集每个对象的字符串输出,并返回结果,这确实使事情变得更容易。

class MenuItem {
    function __construct( $id = 0 ) 
    {
        if ( !isset( $this->MenuID ) ) $this->MenuID = $id;
    }
    function get_children() 
    {
        global $mysqli;
        $result = $mysqli->query( "SELECT * FROM menu WHERE MenuParentId={$this->MenuID}" );
        while ( $row = $result->fetch_object( 'MenuItem' ) )
            $this->children[ $row->MenuID ] = $row->get_children();
        return $this;
    }
    function __toString()
    {
        if ( isset( $this->children ) ) {
            $output = "<li><a>{$this->MenuName}</a><ul>" . implode( $this->children ) . "</ul></li>";
        } else {
            $output = "<li><a>{$this->MenuName}</a></li>";
        }
        return $output;
    }
}
// build menu
$menu = new MenuItem();
$menu->get_children();  // e.g. $menu->get_children( 3 ); for partial build
// show menu
echo '<ul>' . implode( $menu->children ) . '</ul>';

感谢大家的帮助,它对我理解PHP的方式有很大帮助,我找到了一个我喜欢的解决方案。我想和你分享我的方法,我认为它很容易理解,但仍然做它的工作。

首先,我需要一个类来满足我的需求,因为我只是使用一个多类系统,我有一个模型类来定义字段并做一些其他的事情。

下面是该文件的核心:

class Menus{
    // Defining the Fields of this class, which are just the field names in our database (I slightly hanged them)
    private $menus_id;
    private $menus_name;
    private $menus_parent_id;
    // This is not actualy a field in our database but will be later used as a container for the child elements
    private $menus_child_obj;
    // Constructor fills the class with all available data
    public function __construct($arr = array()) {
        foreach ( $arr as $key => $val ) {
            if (property_exists ( get_class($this), $key )) {
                $this->$key = $val;
            }
        }
    }
    // This function allows to change data after the object is bulid, quite easy
    public function autoSetter($fieldName,$value) {
        $this->$fieldName = $value;
    }
    // This function allows accessing the data of the object
    public function autoGetter($fieldName) {
        return $this->$fieldName;
    }
}

接下来,有一个为输出准备数据的类,但我不会在这里详细说明,因为它不是这个问题的主题。

class MenusClass extends MenusADB{
    // This is the function which handles the output for me, I'll skip most of the content here
    function getMenusUl(){
         $menusObj = parent::getMenusObjADB(0);
         // Handle content
    }
}

我们终于到了神奇发生的地方,你可能会注意到另一个类被扩展了,我不会展示它,因为根据这个主题,它所做的只是建立一个PDO连接。

class MenusADB extends RootClass{
    // This are some variables used in some other functions in this class aswell, it is not neccessery to do it this way, but as it makes things way more easy in many other cases I just show it to you
    function __construct(){
        parent::__construct();
        $this->table = strtolower(basename(__FILE__, 'ADB.php'));
        $this->primaryKey = strtolower(basename(__FILE__, 'ADB.php')."_id");
    }
    function set($obj){
        return new $this->table($obj);
    }
    // Finaly we get to the recursive function which bulids the wanted oject
    function getMenusObjADB($id){
    try{
        // This is the SQL query which gets all records that have the wanted parent, this allows us to call it with the id of the menu item we are currently building, wich ends up in a loop, till no records are left
        $stmt = $this->db->prepare("
                                SELECT
                                  *
                                FROM
                                  {$this->table}
                                WHERE
                                  menus_parent_id = :id
                                ");
        $stmt->bindValue('id', $id);
        $stmt->execute();
        // This is the loop which gets all the objects
        while($res = $stmt->fetch(PDO::FETCH_ASSOC)){
            // First we set a new Array with the object filled with all the information of the currently looped Item
            $ret[] = $this->set($res);
            // Next we check if this Item has children by calling this same function but this time with the id parameter of the item we have just build
            if($this->getMenusObjADB($res[$this->primaryKey]) != null){
                // If so we add them to our container we defined in the first class, therefore we need to acces the Arraykey we put our current item in, this is done by first using end($ret) and key($ret), for more details pleasy visit php.net
                end($ret);
                $ret[key($ret)]->autoSetter('menus_child_obj', $this->getMenusObjArrADB($res[$this->primaryKey]));
            }
        }
        // At this point we check if the query has got any results (If there have been items with the wanted parent_id)
        if(empty($ret)){
            return null;
        }
        // If so we return our newly build object
        return $ret;
    }catch(PDOException $ex){
        print "<pre>";
        print_r($ex);
        print "</pre>";
        exit;
    }
}
}

我希望这能帮助人们寻找解决方案,再次感谢你们所有人通过提供这么多的帮助来帮助我找到我的道路!

Chris

// Another way much simpler
// Menu Array multidimensional
$menu = array(
    'page_1' => array(
        'text' => 'page 1',
        'url' => 'page_1.html',
        'children' => array()
    ),
    'page_2' => array(
        'text' => 'page 2',
        'url' => 'page_2.html',
        'children' => array(
            'sub_menu_1' => array(
                'text' => 'sub menu 1',
                'url' => 'sub_menu_1.html',
                'children' => array(
                    'sub_menu_1_2' => array(
                        'text' => 'sub menu 12',
                        'url' => 'sub_menu_1_2.html',
                        'children' => array()
                    )
                )
            ),
            'sub_menu_2' => array(
                'text' => 'sub menu 2',
                'url' => 'sub_menu_2.html',
                'children' => array()
            ),
            'sub_menu_3' => array(
                'text' => 'sub menu 3',
                'url' => 'sub_menu_3.html',
                'children' => array()
            ),
            'sub_menu_4' => array(
                'text' => 'sub menu 4',
                'url' => 'sub_menu_4.html',
                'children' => array()
            )
        )
    ),
    'page_3' => array(
        'text' => 'page 3',
        'url' => 'page_3.html',
        'children' => array()
    )
);
// Make menu
function makeMenu($menu_array,$is_sub = false,$list=['ul','li']){
    $attr  = (!$is_sub) ? ' class="menu"' : ' class="submenu"';
    $child = NULL;
    $menu = "<{$list[0]}{$attr}>";
    foreach($menu_array as $id => $items){
        foreach($items as $key => $val){
            if( is_array($val) ) {
                if ( !empty($val) )  {
                    $child = makeMenu($val,true,$list);
                }
            } else {
                $$key = $val;
            }
        }//foreach
        $menu .= "<{$list[1]}>";
            $menu .= '<a href="'.$url.'">'.$text.'</a>';
            $menu .= $child; // Sub Menu
        $menu .= "</{$list[1]}>";
        unset($child);
    }//foreach
    $menu .= "</{$list[0]}>";
    return $menu;
}
// retrieve the desired page with the shaft
function makeSubMenu($menu, $key) {
   return makeMenu(array($menu[$key]));
}
// chain Article
function chainItem(array $array, $unset = '' ){
    $ds = array();
    if ( is_array($array) )
        foreach((array)$unset as $arr) unset($array[$arr]);
        foreach($array as $key=>$value)
            if (!is_array($value)) $ds[$key] = $value;
    return $ds;
}
// Build walk recursive -> single array insert database MySql
function walkRecursive($array, $ordId = true, $unset=[], $children = 'children',  $i = 1, $parent = 0, &$res = [] ) {
    if ( !is_array($array) ) return array();
    foreach(array_values($array) as $key=>$arr) {
        $das = array( 
            'id' => $id = $ordId ? $arr['id'] : $i++,
            'parent' => $parent
        );
        $res[] = array_merge($das,chainItem($arr,$unset));
        if( isset($arr[$children]) ) {
            walkRecursive($arr[$children], $ordId, $unset, $children, $i, $id, $res );
        }
    }
    return $res;
}
echo makeMenu($menu);
// Result of the print :
<ul class="menu">
    <li>
        <a href="page_1.html">page 1</a>
    </li>
    <li>
        <a href="page_2.html">page 2</a>
        <ul class="submenu">
            <li>
                <a href="sub_menu_1.html">sub menu 1</a>
                <ul class="submenu">
                    <li>
                        <a href="sub_menu_1_2.html">sub menu 12</a>
                    </li>
                </ul>
            </li>
            <li>
                <a href="sub_menu_2.html">sub menu 2</a>
            </li>
            <li>
                <a href="sub_menu_3.html">sub menu 3</a>
            </li>
            <li>
                <a href="sub_menu_4.html">sub menu 4</a>
            </li>
        </ul>
    </li>
    <li>
        <a href="page_3.html">page 3</a>
    </li>
</ul>

// Build walk recursive -> single array insert database MySql
header("Content-type: text/plain");
print_r( walkRecursive($menu,false,['parent','id']) );
// Print array -> Result:
Array
(
    [0] => Array
        (
            [id] => 1
            [parent] => 0
            [text] => page 1
            [url] => page_1.html
        )
    [1] => Array
        (
            [id] => 2
            [parent] => 0
            [text] => page 2
            [url] => page_2.html
        )
    [2] => Array
        (
            [id] => 3
            [parent] => 2
            [text] => sub menu 1
            [url] => sub_menu_1.html
        )
    [3] => Array
        (
            [id] => 4
            [parent] => 3
            [text] => sub menu 12
            [url] => sub_menu_1_2.html
        )
    [4] => Array
        (
            [id] => 4
            [parent] => 2
            [text] => sub menu 2
            [url] => sub_menu_2.html
        )
    [5] => Array
        (
            [id] => 5
            [parent] => 2
            [text] => sub menu 3
            [url] => sub_menu_3.html
        )
    [6] => Array
        (
            [id] => 6
            [parent] => 2
            [text] => sub menu 4
            [url] => sub_menu_4.html
        )
    [7] => Array
        (
            [id] => 3
            [parent] => 0
            [text] => page 3
            [url] => page_3.html
        )
)