PHP 变量作用域和“foreach”

发布于 2024-11-18 17:33:01 字数 1451 浏览 4 评论 0原文

我的应用程序正在构建 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.        
    }
  }
}

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

文档布局如下:

1)封面

2)目录

3)页面 A

4)页面 B

5)页面 C

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

如果我从 TableOfContents.php 中删除 foreach,则所有连续页面都会正确渲染。

我觉得这是指针和变量作用域的问题,那么我该怎么解决呢?

My application is building PDF documents. It uses scripts to produce each page's HTML.
The PDF-Generating class is "Production", and page class is "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.        
    }
  }
}

Here is the Page class summary:

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

}

I've encountered a problem when coding Table of Contents. It accesses Production, get all the pages, queries them, and builds TOC based on page titles:

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

What happens is that foreach inside the TableOfContents.php is interfering with foreach in Production. Production foreach loop is terminated at Index page (which is actually a second page in the document, after the cover page).

The Document Layout is like so:

1) Cover Page

2) Table of Contents

3) Page A

4) Page B

5) Page C

TableOfContents.php, in its foreach loop, goes through the pages as required and builds an index of the entire document, but the loop in Production terminates at Table of Contents and does not proceed to render Pages A, B and C.

If I remove foreach from TableOfContents.php, all consecutive pages are rendered appropriately.

I feel it's a problem with the pointer and variable scope, so what can I do to fix it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

肩上的翅膀 2024-11-25 17:33:01

诊断

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

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

解决方案

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

对于真正的修复,我建议查看 $_pages 中对象后面的类,看看是否可以更改该类。不要让 $_pages 成为迭代器,而是更改 $_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.
 */

Diagnosis

I suspect the problem is that $_pages isn't a normal PHP array, but instead an object which happens to implement the Iterator interface. Because of this, the "state" of the foreach loop is stored on the object itself, which means that the two loops are conflicting.

If $_pages was a plain array, then there would be no problem, since the line $pages = $this->production->getPages(); would make a copy, since PHP arrays are copied on assignment (unlike objects) and also because nested foreach loops on a normal array don't have that problem. (Presumably from some internal array copy/assignemnt logic.)

Solution

The "fast and dirty" fix is to avoid foreach loops, but I think that would be both annoying and be a cause of future bugs because it's very easy to forget that $_pages needs super-special treatment.

For a real fix, I suggest looking at whatever class is behind the object in $_pages and see if you can change that class. Instead of having $_pages be the Iterator, change $_pages so that it provides iterators through the IteratorAggregate interface.

That way every foreach loop asks for a separate iterator object and maintains separate state.

Here is a sample script to illustrate the problem, partially cribbed from the PHP reference pages:

<?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.
 */
掩饰不了的爱 2024-11-25 17:33:01

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

I'm not sure to understand what is your problem, but you may look at the PHP function reset =)

沒落の蓅哖 2024-11-25 17:33:01

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

The solution was the avoid using foreach and use conventional loops, as suggested here:
nested foreach in PHP problem

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文