douzhuo1858 2015-06-08 11:59
浏览 25

第一个循环在递归函数php中重启

I'm trying to create a series of <ul> and <li> to create a directory/file structure to navigate some files created from a table in a dB. The table (tb_lib_manual) contains both files and folders.

If a record has a null entry for fileID then it is a folder not a file. each record has a parentID to show which folder is parent, for files and folders in the root this is 0.

The php code is thus:

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();
    }

}

The problem is when the code returns back to the while loop after calling itself it restarts the loop rather than carrying on where it left off, causing a repeat in the child folder. Is there either a better way to do this or am I doing something wrong!?

Table looks like this:

enter image description here

output like this: '

  • A Manual
  • A Manual
  • Folder 1
  • Nasa exercise
  • A Manual
  • A Manual
  • Folder 1
  • Nasa exercise
'
  • 写回答

2条回答 默认 最新

  • douchui4459 2015-06-08 12:34
    关注

    Yes, there are better ways of doing this.

    If you fetch the whole tree every time, you might as well load all the nodes into a big array (of objects), put each node's id as index and then loop over the array once to create the parent references by for example

    // 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;
        }
    }
    

    Complexity: You do one query and you have to iterate all your nodes once.

    For this to work your nodes need to be objects. Otherwise each assignment to $node->children will create a copy of the assigned node where you wanted a reference.

    If you do not want to fetch all nodes, you can go through your tree level by level by creating a list of node ids from the previous level.

    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
    

    Complexity: Number of queries <= height of your tree. You only iterate each fetched node once.

    For displaying the tree as html list you iterate once more:

    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>";
            }
        }
    }
    

    Some unrelated remarks:

    • instead of a hard-coded style="display:none" you could just use a css rule like ul li ul {display:none} to hide all lists below root
    • I split fetching the data from the database and converting the data for displaying into two separate scripts, which is the standard when you develop using MVC. If you don't want to do this run the scripts in succession.
    • PHP itself is a template engine, but consider using another template engine to display your html like Smarty. I personally prefer smarty for it's simpler syntax.
    • stackoverflow answer on how to bind a PHP array to a MySQL IN-operator using prepared statements
    • if you need to fetch subtrees often consider using a special table structure to reduce the number of queries
    评论

报告相同问题?

悬赏问题

  • ¥15 unity第一人称射击小游戏,有demo,在原脚本的基础上进行修改以达到要求
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b
  • ¥20 BAPI_PR_CHANGE how to add account assignment information for service line
  • ¥500 火焰左右视图、视差(基于双目相机)
  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染