PHP:循环遍历多维数组并在数组项之间建立父子关系

4
我正在开发一个内容管理系统,遇到了关于CMS中父子项关系的问题。
基本上,我有一个可以创建页面的系统,当创建页面时,你可以选择一个父页面作为子导航。这一切都很好,直到我尝试从数据库生成导航。
我不确定是否应该使用某种连接方法,但我更喜欢将所有数据存入数组中,并使用php操作该数组。
来自数据库的结果数组如下:
Array
(
    [0] => Array
    (
        [id] => 27
        [name] => home
        [link] => home.html
        [parent] => 0
    )

    [1] => Array
    (
        [id] => 30
        [name] => about
        [link] => about.html
        [parent] => 27
    )
)

我需要循环遍历一个数组,这个数组可以有任意数量的导航,并智能地将其排序为父子关系。我已经能够做到一层深度的循环遍历,但它需要管理具有无限层级的子元素和子元素等等,并将其输出为HTML无序嵌套列表。

<ul>
  <li>
  <a>Nav</a>
     <ul>
        <li>
           <a>Subnav</a>
             <ul>
                 <li>
                    <a>Sub Sub nav</a>
                 </li>
             </ul>
        </li>
     </ul>
  <li>
<ul>

等等……


数据生成过程中是否有什么保证父子关系始终构成一棵树的机制?否则,循环依赖会破坏你尝试渲染父子关系“图形”的计划。 - Peter
4个回答

14

我认为你不应该涉及到对象。此外,我认为生成对象等只会增加额外的工作量。在我看来,你应该遍历数组并生成一个表示导航层次结构的多维数组,然后递归地遍历生成的数组以生成HTML。我已经为您编写了示例代码,它按照您想要的方式工作,但您可能需要做一些更改。

函数

// Generate your multidimensional array from the linear array
function GenerateNavArray($arr, $parent = 0)
{
    $pages = Array();
    foreach($arr as $page)
    {
        if($page['parent'] == $parent)
        {
            $page['sub'] = isset($page['sub']) ? $page['sub'] : GenerateNavArray($arr, $page['id']);
            $pages[] = $page;
        }
    }
    return $pages;
}

// loop the multidimensional array recursively to generate the HTML
function GenerateNavHTML($nav)
{
    $html = '';
    foreach($nav as $page)
    {
        $html .= '<ul><li>';
        $html .= '<a href="' . $page['link'] . '">' . $page['name'] . '</a>';
        $html .= GenerateNavHTML($page['sub']);
        $html .= '</li></ul>';
    }
    return $html;
}

** 示例用法 **

$nav = Array
(
    Array
    (
        'id' => 27,
        'name' => 'home',
        'link' => 'home.html',
        'parent' => 0
    ),
    Array
    (
        'id' => 30,
        'name' => 'about',
        'link' => 'about.html',
        'parent' => 27
    )
);

$navarray = GenerateNavArray($nav);
echo GenerateNavHTML($navarray);

你可能可以一步完成两件事情,但我认为先生成多维数组更整洁。祝好运!


太棒了!谢谢,我走在正确的轨道上,我只循环了一次,所以它只到了一个级别。运行得很好。谢谢。 - Laurence

3
Saad Imran的解决方案可以正常工作,但是如果您想在同一个列表中有多个导航项,则需要更改一些行以生成经过验证的项目列表。我还添加了缩进来使生成的代码更易读。我使用空格,但如果您喜欢,这可以轻松更改为制表符,只需用"\t"替换4个空格即可。(请注意,必须使用双引号,而不是单引号,否则php不会替换为制表符字符,而实际上是 \t )。
我正在使用codeigniter 2.1.0和superfish(PHP5)。
function GenerateNavHTML($nav, $tabs = "")
{
    $tab="    ";
    $html = "\n$tabs<ul class=\"sf-menu\">\n";
    foreach($nav as $page)
    {
        //check if page is currently being viewed
        if($page['link'] == uri_string()) {
            $html .= "$tabs$tab<li class=\"current\">";
        } else {
            $html .= "$tabs$tab<li>";
        }
        $html .= "<a href=\"$page[link]\">$page[name]</a>";
        //Don't generate empty lists
        if(isset($page['sub'][0])) {
            $html .= $this->GenerateNavHTML($page['sub'], $tabs.$tab);
        }
        $html .= "</li>\n";
    }
    $html .= $tabs."</ul>\n";

    return $html;
}

我正在获取(为了澄清而切换到有序列表)
1. Home  
1. About Us  
1. Products  
    1. sub-product 1  
    1. sub-product 2  
1. Contact  

现在我明白了。
1. Home  
2. About Us  
3. Products  
    1. sub-product 1  
    2. sub-product 2  
4. Contact  

漂亮的生成HTML
<ul class="sf-menu">
    <li class="current"><a href="index.html">Home</a></li>
    <li><a href="about.html">About Us</a></li>
    <li><a href="products.html">Products</a>
        <ul class="sf-menu">
            <li><a href="products-sub1.html">sub-product 1</a></li>
            <li><a href="products-sub2.html">sub-product 2</a></li>
        </ul>
    </li>
    <li><a href="contact.html">Contact</a></li>
</ul>

1

最简单的方法可能是使用对象。这些对象可以很容易地进行操作,并且可以从数据库中获取的数组轻松创建。

例如,每个对象都有:

  1. ID
  2. 名称
  3. 链接
  4. 对父对象的对象引用
  5. 子对象列表

您需要一个静态函数,能够找出具有特定数据库ID的对象。在创建新对象时,通过将引用放入静态列表中来完成此操作。然后可以调用该列表并在运行时进行检查。

其余部分非常简单:

$arr = get_array_from_database();
foreach($arr as $node){
    $parent_object = get_parent_object($node_id);
    $parent_object.subnodes[] = new NodeObject($node);
}

关于返回列表中的对象,最好使用递归来完成;

function return_in_list($objects)
{
    foreach($objects as $node)
    {
        echo '<li>';
        echo '<a>' + node.link + '</a>';

        if(node.subnodes.length > 0)
        {
            echo '<ul>';
            return_in_list($node.subnodes);
            echo '</ul>';
        }
    puts '</li>'
    }
}

0
我正在开发同一个项目,但我并没有发现需要使用对象。数据库基本上可以处理结构,而php函数可以完成其余工作。我的解决方案是为页面添加一个指向部分名称的父字段,就像您一样,但还要为部分表添加一个父字段,以便部分可以将其他部分作为父级指向。由于只有两个父字段需要跟踪,每个表一个,所以仍然非常易于管理,并且在未来我可以嵌套我的结构多个层次。
因此,对于动态树创建,我将递归检查父项,直到遇到空值,这表示当前元素位于文档根上。这样,我们不需要在函数代码中了解当前页面结构的任何细节,而可以专注于在mysql中添加和排列页面。由于另一个帖子展示了一些菜单html,所以我想在这里添加一个动态面包屑路径的示例,因为主题非常相似。
数据
// Sample 3 x 2 page array (php / html pages)
// D1 key = page name
// D2 element 1 = page name for menu display
// D2 element 2 = parent name (section)
$pages = Array
(
  'sample-page-1.php' => Array ( 'M' => 'page 1', 'P' => null ),
  'sample-page-2.php' => Array ( 'M' => 'page 2', 'P' => 'hello' ),
  'sample-page-3.php' => Array ( 'M' => 'page 3', 'P' => 'world' )
);

// Sample 2 x 1 section array (parent directories)
// D1 key = section name
// D2 element = parent name (if null, assume root)
$sections = Array
( 
  'hello' => null,
  'world' => 'hello'
);

$sep = ' > ';       // Path seperator
$site = 'test.com'; // Home string

函数

// Echo paragraph to browser
function html_pp ( $text )
{
  echo PHP_EOL . '<p>' . sprintf ( $text ) . '</p>' . PHP_EOL;
}

// Get breadcrumb for given page
function breadcrumb ( $page )
{
  // Reference variables in parent scope
  global $pages;
  global $sections;
  global $sep;
  global $site;

  // Get page data from array
  $menu   = $pages [ $page ] [ 'M' ];
  $parent = $pages [ $page ] [ 'P' ];

  if  ( $parent == null )
  {
    $path = $site . $sep . $menu;
  }
  else
  {
    $path = $site . $sep . get_path ( $parent ) . $sep . $menu;
  }
  return $path;
}

// Trace ancestry back to root
function get_path ( $parent )
{
  // Reference variables in parent scope
  global $sections;
  global $sep;

  if ( $sections [ $parent ] == null )
  {
    // No more parents
    return $parent;
  }
  else
  {
    // Get next parent through recursive call
    return get_path ( $sections [ $parent ] ) . $sep . $parent;
  }
}

用法

// Get breadcrumbs by page name
$p1 = 'sample-page-1.php';
$p2 = 'sample-page-2.php';
$p3 = 'sample-page-3.php';

html_pp ( $p1 . ' || ' . breadcrumb ( $p1 ) );
html_pp ( $p2 . ' || ' . breadcrumb ( $p2 ) );
html_pp ( $p3 . ' || ' . breadcrumb ( $p3 ) );

// or use foreach to list all pages
foreach ( $pages as $page => $data)
{
  html_pp ( $page . ' || ' . breadcrumb ( $page ) );
}

输出

sample-page-1.php || test.com > 页面 1

sample-page-2.php || test.com > 你好 > 页面 2

sample-page-3.php || test.com > 你好 > 世界 > 页面 3


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接