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

时间:2014-06-20 14:37:39

标签: php mysql arrays menu

我已经找到了这个问题的解决方案&我只找到了直接显示菜单的解决方案,因为我需要后端的菜单结构进行编辑以及学习最有效的编程方法我不想直接打印菜单但是用树结构填充数组

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

$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个Sublevel,或者我知道有多少个Sublevels被给出,那就不是问题,但事实并非如此应该能够添加尽可能多的Sublevels。​​

6 个答案:

答案 0 :(得分:0)

我不会通过他们的id引用事物,因为这使得在读取代码时很难知道菜单项的功能。我会使用PHP的关联数组并给它们一个明确的键。你必须确保每个键都是唯一的,但是你也可以使唯一键与显示的不同,所以如果你有两个不同的菜单项叫honda,一个是honda_cars,另一个是honda_streetfighter,但两个都是'本田”。

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

答案 1 :(得分:0)

我最近处理过类似的问题,我们搜索了一些很好的解决方案,因为我们有很多子级别和大量数据。 我建议你去研究一下Nested set

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

优点:

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

缺点:

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

我想还有更多,但这些是我在使用它之后可以告诉的。

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

希望它有所帮助,如果您找到其他方法,请告诉我们!

答案 2 :(得分:0)

与您发布的表结构类似的表结构可以使用递归算法构建层次结构,如下所示:

//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浏览器。

答案 3 :(得分:0)

以下是一些可能对您有帮助的链接:

在两篇文章中都解释了两种最常见的方法,邻接列表模型(您正在使用的模型)和嵌套集模型。对于更大的数据集,我将推荐嵌套模型,对于较小的邻接模型,它将足够快。

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

MenuItem Object(
    [MenuID] => 0
    [children] => Array (
         [0] => MenuItem Object (
                [MenuID] => 1
                [MenuName] => Paintings
                [MenuParentId] => 0
                )
         [3] => MenuItem Object (
                [MenuID] => 3
                [MenuName] => Vehicles
                [MenuParentId] => 0
                [children] => Array (
                     [2] => MenuItem Object (
                           [MenuID] => 2
                           [MenuName] => Cars
                           [MenuParentId] => 3
                           [children] => Array 
                               (
                                  ...
                               )
                           )
                     [4] => MenuItem Object (
                           [MenuID] => 4
                           [MenuName] => Boats
                           [MenuParentId] => 3
                           )
                     )
                )
        )
)

在某些时候,您需要打印菜单。 PHP有magic method __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>';

答案 4 :(得分:0)

谢谢大家的帮助,这对我理解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;
    }
}

}

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

Greating

克里斯

答案 5 :(得分:0)

// 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
        )

)