开发者

PHP Variable Scope and "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 = $product开发者_如何学Goion; 
    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?


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.
 */


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


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜