【问题标题】:First loop restarting in recursive function php在递归函数php中重新启动第一个循环
【发布时间】:2015-08-22 21:18:54
【问题描述】:

我正在尝试创建一系列<ul><li> 来创建目录/文件结构来导航从以dB 为单位的表创建的一些文件。表 (tb_lib_manual) 包含文件和文件夹。

如果记录的 fileID 为空条目,则它是文件夹而不是文件。每条记录都有一个 parentID 来显示哪个文件夹是父文件夹,对于根目录中的文件和文件夹,这是 0。

php 代码是这样的:

class library_folders extends system_pageElement 
{
    private $html = '';
    private $i = 0;
    private $stmtArray = array();
    private $objectArray = array();

    function __construct() 
    {
        parent::__construct();
        $this->nextList();
    }


    function nextList($parentID = 0) 
    {
        $qSQL = 'SELECT * FROM tb_lib_manual WHERE parentID=:parentID';
        $stmtArray[$this->i] = $this->dbConnection->prepare($qSQL);
        $stmtArray[$this->i]->bindValue(':parentID', $parentID, PDO::PARAM_INT);
        $stmtArray[$this->i]->execute();
        if($stmtArray[$this->i]->rowCount() > 0)
        {
            $display ='';
            if($parentID != 0)
            {
                $display = ' style="display:none"';
            }
            $this->html .= '<ul' . $display . '>';
        }


        while ($this->objectArray[$this->i] = $stmtArray[$this->i]->fetchObject()) 
        {
            $this->html .= '<li>' . $this->objectArray[$this->i]->title;
            if($this->objectArray[$this->i]->fileID == null)
            {
                //we have a folder!
                $manualID = $this->objectArray[$this->i]->manualID;
                $this->i ++;
                $this->nextList($manualID);
                $this->i--;
            }
            $this->html .= '</li>';
        }


        if($stmtArray[$this->i]->rowCount() > 0)
        {
            $this->html .= '</ul>';
        }   

        echo $this->html;
    }   
function __destruct()
    {
        parent::__destruct();
    }

}

问题是当代码在调用自身后返回到 while 循环时,它会重新启动循环,而不是继续它离开的地方,从而导致子文件夹中的重复。有没有更好的方法来做到这一点,还是我做错了什么!?

表格如下:

输出如下: '

  • 手册
  • 手册
  • 文件夹 1
  • NASA 演习
  • 手册
  • 手册
  • 文件夹 1
  • NASA 演习
'

【问题讨论】:

  • manualID和ParentID一样吗?
  • 是的,很抱歉应该解释一下,显然不同的列...
  • 您可以放置​​一些变量的调试输出,例如var_dump($manualID);
  • 嗨,抱歉不知道如何放置调试输出,因为我无法让我的调试工作(aptana studio 3)但是表数据看起来像这样(刚刚编辑的问题)
  • 我对@9​​87654326@ 中的内容更感兴趣。我认为我们错过了课程的其他重要部分

标签: php mysql recursion pdo while-loop


【解决方案1】:

修正了代码,很傻的错误,在错误的地方回显……

class library_folders extends system_pageElement 
{
    private $html = '';
    private $i = 0;
    private $stmtArray = array();
    private $objectArray = array();

    function __construct() 
    {
        parent::__construct();

        $this->nextList();

        echo $this->html;

    }


    function nextList($parentID = 0) 
    {
        $qSQL = 'SELECT * FROM tb_lib_manual WHERE parentID=:parentID';
        //echo $this->i;        
        $stmtArray[$this->i] = $this->dbConnection->prepare($qSQL);
        $stmtArray[$this->i]->bindValue(':parentID', $parentID, PDO::PARAM_INT);
        $stmtArray[$this->i]->execute();
        if($stmtArray[$this->i]->rowCount() > 0)
        {
            $this->html .= '<ul>';
        }

        while ($this->objectArray[$this->i] = $stmtArray[$this->i]->fetchObject()) 
        {
            $this->html .= '<li>' . $this->objectArray[$this->i]->title;
            if($this->objectArray[$this->i]->fileID == null)
            {
                //we have a folder!
                $manualID = $this->objectArray[$this->i]->manualID;
                $this->i ++;
                $this->nextList($manualID);
                $this->i--;
            }
            $this->html .= '</li>';
        }


        if($stmtArray[$this->i]->rowCount() > 0)
        {
            $this->html .= '</ul>';
        }   

    }   

    function __destruct()
    {
        parent::__destruct();
    }

}

【讨论】:

    【解决方案2】:

    是的,有更好的方法来做到这一点。

    如果你每次都取整棵树,你不妨将所有节点加载到一个大数组(对象)中,将每个节点的 id 作为索引,然后在数组上循环一次以例如通过创建父引用

    // create a fake root node to start your traversal later on
    // only do this if you don't have a real root node 
    // which I assume you don't
    $root = (object) ["children" => []];
    
    // loop over all nodes
    foreach ($nodes as $node)
    {
        // if the node has a parent node that is not root
        if ($node->parentId > 0)
        {
             // put it into it's parent's list of children
             $nodes[ $node->parentId ]->children[] = $node;
        }
        else
        {
             // otherwise put it into root's list of children
             $root->children[] = $node;
        }
    }
    

    复杂性:您只进行一次查询,就必须对所有节点进行一次迭代。

    为此,您的节点需要是对象。否则,对$node-&gt;children 的每个分配都会创建一个您想要引用的已分配节点的副本。

    如果您不想获取所有节点,您可以通过创建上一级的节点 ID 列表逐级遍历树。

    function fetchLevel ($nodes, $previousLevelIds)
    {
        // get all children from the previous level's nodes
        // I assume $previousLevelIds to be an array of integers. beware of sql injection
        // I refrained from using prepared statements for simplicity
        $stmt = $pdo->query("SELECT id, parentId FROM nodes WHERE parentId IN (".implode(",",$previousLevelIds).")");
    
        // fetch nodes as instances of stdclass
        $currentLevelNodes = $stmt->fetchAll(PDO::FETCH_OBJ);
    
        $nextLevelIds = [];
        foreach ($currentLevelNodes as $node)
        {
             // ids for next level
             $nextLevelIds[] = $node->id;
    
             // parent <-> child reference
             $nodes[ $node->parentId ]->children[] = $node;
        }
    
        // fetch the next level only if we found any nodes in this level
        // this will stop the recursion
        if ($nextLevelIds)
            fetchLevel($nodes, $nextLevelIds);
    }
    
    // start by using a fake root again
    $root = (object) ["id" => 0, "children" => []];
    $nodes = [0 => $root];
    fetchLevel($nodes, [0]);
    
    // or start with a specific node in the tree
    $node = $pdo->query("SELECT id, parentId FROM nodes WHERE id = 1337")->fetch(PDO::FETCH_OBJ);
    $nodes = [$node->id => $node];
    fetchLevel($nodes, [$node->id]);
    
    // or a number of nodes which don't even have to 
    // be on the same level, but you might fetch nodes multiple times
    // if you it this way
    

    复杂性:查询数

    为了将树显示为 html 列表,您再次迭代:

    class Foo {
    
        public $html;
    
        public function getList ($nodes)
        {
             // outer most ul
             $this->html = '<ul>';
    
             $this->recurseList($nodes);
    
             $this->html .= '</ul>';
        }
    
        protected function recurseList ($nodes)
        {
            foreach ($nodes as $node)
            {
                $this->html .= "<li><span>".$node->name."</span>";
    
                if ($node->children)
                {
                    if ($node->parentId > 0)
                        $this->html .= '<ul style="display:none">';
                    else
                        $this->html .= '<ul>';
    
                    $this->recurseList($node->children);
    
                    $this->html .= "</ul>";
                }
    
                $this->html .= "</li>";
            }
        }
    }
    

    一些不相关的言论:

    • 您可以使用 ul li ul {display:none} 之类的 CSS 规则来隐藏根目录下的所有列表,而不是硬编码的 style="display:none"
    • 我拆分从数据库中获取数据并将数据转换为两个单独的脚本,这是您使用MVC开发时的标准。如果您不想这样做,请连续运行脚本。
    • PHP 本身是一个模板引擎,但请考虑使用另一个模板引擎来显示您的 html,例如 Smarty。我个人更喜欢 smarty,因为它的语法更简单。
    • stackoverflow 回答如何使用准备好的语句将 PHP 数组绑定到 MySQL IN 运算符
    • 如果您需要获取子树,通常可以考虑使用a special table 结构来减少查询次数

    【讨论】:

    • 谢谢 Basti,修复了我的代码,但我想我开始明白为什么你的解决方案更好了,但我还没有完全理解,因为我是新手......跨度>
    • 如果您有兴趣学习并有疑问,请尽管问。由于您自己在代码中发现了问题,请输入您自己的答案来解释它并接受它。 ;-)
    猜你喜欢
    • 2014-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-25
    • 2014-07-21
    • 1970-01-01
    • 2012-01-03
    • 2023-03-24
    相关资源
    最近更新 更多