zend框架-PHP可变范围和";foreach"


zend framework - PHP Variable Scope and "foreach"

我的应用程序正在构建PDF文档。它使用脚本来生成每个页面的HTML。PDF生成类是"Production",页面类是"page"。

class Production
{
  private $_pages; // an array of "Page" objects that the document is composed of
  public getPages()
  {
    return $this->_pages; 
  }
  public render()
  {
    foreach($this->_pages as $page) {
      $pageHtml = $page->getHtml($this); // Page takes a pointer to production to access some of its data.        
    }
  }
}

以下是页面类摘要:

class Page 
{
  private $scriptPath; // Path to Script File (PHP)
  public function getHtml(Production &$production)
  {
    $view = new Zend_View();
    $view->production = $production; 
    return $view->render($this->scriptPath); 
  }
}

我在编码目录时遇到了一个问题。它访问Production,获取所有页面,查询它们,并根据页面标题构建TOC:

// TableOfContents.php 
// "$this" refers to Zend_View from Pages->getHtml();
$pages = $this->production->getPages();
foreach($pages as $page) {
  // Populate TOC
  // ...
  // ...
}

发生的情况是TableOfContents.php中的foreach正在干扰Production中的foreach。生产foreach循环在Index页(实际上是文档中封面之后的第二页(终止。

文档布局如下:

1( 封面

2( 目录

3( 页面A

4( 第B页

5( 第C页

TableOfContents.php在其foreach循环中,根据需要遍历页面并构建整个文档的索引,但Production中的循环终止于目录,并且不继续呈现页面A、B和C。

如果我从TableOfContents.php中删除foreach,那么所有连续的页面都会得到适当的呈现。

我觉得这是指针和变量作用域的问题,那么我能做些什么来解决它呢?

诊断

我怀疑问题在于$_pages不是一个普通的PHP数组,而是一个恰好实现Iterator接口的对象。因此,foreach循环的"状态"存储在对象本身上,这意味着这两个循环是冲突的。

如果$_pages是一个普通数组,那么就没有问题了,因为$pages = $this->production->getPages();行会进行复制,因为PHP数组是在赋值时复制的(与对象不同(,也因为普通数组上的嵌套foreach循环没有这个问题。(可能来自某些内部阵列复制/分配器逻辑。(

解决方案

"快速而肮脏"的修复方法是避免foreach循环,但我认为这既令人讨厌,也会导致未来的错误,因为很容易忘记$_pages需要超级特殊的处理。

对于真正的修复,我建议查看$_pages中对象后面的任何类,看看是否可以更改该类。更改$_pages,使其通过IteratorAggregate接口提供迭代器,而不是使$_pages成为Iterator

这样,每个foreach循环都要求一个单独的迭代器对象,并保持单独的状态。

下面是一个示例脚本来说明这个问题,部分是从PHP参考页面中抄袭的:

<?php
class MyIterator implements Iterator
{
    private $var = array();
    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }
    public function rewind()
    {
        reset($this->var);
    }
    public function current()
    {
        $var = current($this->var);
        return $var;
    }
    public function key() 
    {
        $var = key($this->var);
        return $var;
    }
    public function next() 
    {
        $var = next($this->var);
        return $var;
    }
    public function valid()
    {
        $key = key($this->var);
        $var = ($key !== NULL && $key !== FALSE);
        return $var;
    }
}
// END BOILERPLATE DEFINITION OF ITERATOR, START OF INTERESTING PART
function getMyArrayThingy(){
    /* 
     * Hey, let's conveniently give them an object that
     * behaves like an array. It'll be convenient! 
     * Nothing could possibly go wrong, right?
     */
    return new MyIterator(array("a","b","c"));  
}

// $arr = array("a,b,c"); // This is old code. It worked fine. Now we'll use the new convenient thing!
$arr = getMyArrayThingy();
// We expect this code to output nine lines, showing all combinations of a,b,c 
foreach($arr as $item){
        foreach($arr as $item2){
                echo("$item, $item2'n");
        }
}
/* 
 * Oh no! It printed only a,a and a,b and a,c! 
 * The outer loop exited too early because the counter
 * was set to C from the inner loop.
 */

我不确定你的问题是什么,但你可以看看PHP函数reset=(

解决方案是避免使用foreach并使用常规循环,如下所示:PHP问题中的嵌套foreach