4

我的应用程序正在构建 PDF 文档。它使用脚本来生成每个页面的 HTML。PDF-Generating 类是“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.        
    }
  }
}

这是 Page 类摘要:

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 干扰了生产中的 foreach。生产 foreach 循环在索引页(实际上是文档中的第二页,在封面页之后)处终止。

文档布局是这样的:

1) 封面

2) 目录

3) 页面 A

4) 页面 B

5) 页 C

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

如果我从 TableOfContents.php 中删除 foreach,所有连续的页面都会正确呈现。

我觉得这是指针和变量范围的问题,那么我该怎么做才能解决它?

4

3 回答 3

3

诊断

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

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

解决方案

“快速而肮脏”的修复是为了避免 foreach 循环,但我认为这既烦人又会成为未来错误的原因,因为很容易忘记$_pages需要超级特殊处理。

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

这样,每个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.
 */
于 2011-07-05T19:31:52.030 回答
0

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

于 2011-07-05T19:02:09.120 回答
0

解决方案是避免使用 foreach 并使用常规循环,如下所示: nested foreach in PHP question

于 2011-07-05T19:16:12.630 回答