【问题标题】:PHP: Loop through multidimensional array and establish parent-child relationships between array itemsPHP:循环遍历多维数组,建立数组项之间的父子关系
【发布时间】:2011-12-31 02:10:16
【问题描述】:

我正在开发一个内容管理系统,但我遇到了 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>

等等。 ...

【问题讨论】:

  • 数据的生成有什么东西可以保证父子关系总是给你一棵树吗?否则,当您尝试渲染父子关系的“图表”时,循环依赖会毁了您的一天。

标签: php arrays loops parent-child


【解决方案1】:

我认为你不应该进入对象。另外,我认为生成对象等只是额外的工作。在我看来,您应该循环遍历数组并生成表示导航层次结构的多维数组,然后递归循环生成的数组以生成 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);

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

【讨论】:

  • 太棒了!谢谢,我在正确的轨道上我只循环了一次,所以它只去了一个级别。效果很好。谢谢。
【解决方案2】:

Saad Imran 的解决方案可以正常工作,除非您希望在同一个列表中包含多个导航项。我不得不更改几行以使其生成经过验证的项目列表。我还添加了缩进以使生成的代码更具可读性。我正在使用空格,但如果您愿意,可以轻松地将其更改为制表符,只需将 4 个空格替换为 "\t"。 (注意一定要用双引号,不能用单引号,否则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;
}

我得到了(切换到 ol 以进行澄清)

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>

【讨论】:

    【解决方案3】:

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

    一个例子,每个对象都有:

    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>'
        }
    }
    

    【讨论】:

      【解决方案4】:

      我正在做同一个项目,但我还没有发现需要使用对象。数据库几乎负责结构,而 php 函数可以完成其余的工作。我的解决方案是为页面设置一个父字段,该字段指向一个节名称,就像您一样,但也为节表添加一个父字段,以便节可以指向其他节作为父节。它仍然非常易于管理,因为只有 2 个父字段需要跟踪,每个表一个,并且我可以在将来根据需要嵌套我的结构。

      因此,对于动态树的创建,我将递归地检查父级,直到遇到 null,这表明当前元素位于 doc 根上。这样我们就不需要知道函数代码中当前页面结构的任何细节,我们可以专注于在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 页

      【讨论】:

        猜你喜欢
        • 2012-04-21
        • 2011-01-02
        • 2016-02-16
        • 2021-09-17
        • 1970-01-01
        • 2010-10-24
        相关资源
        最近更新 更多