Переменная Область PHP и "foreach" - PullRequest
4 голосов
/ 05 июля 2011

Мое приложение создает документы PDF. Он использует скрипты для создания HTML каждой страницы. Класс «Создание PDF» - «Производство», а класс страницы - «Страница».

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, получает все страницы, запрашивает их и создает оглавление на основе заголовков страниц:

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

Что происходит, так это то, что foreach внутри TableOfContents.php вмешивается в foreach в Production. Производственный цикл foreach завершается на странице индекса (которая фактически является второй страницей документа после титульной страницы).

Макет документа выглядит так:

1) Титульный лист

2) Содержание

3) Страница A

4) Страница B

5) Страница C

TableOfContents.php, в своем цикле foreach, просматривает страницы по мере необходимости и создает индекс всего документа, но цикл в Production заканчивается на оглавлении и не выполняет рендеринг страниц A, B и C.

Если я удаляю foreach из TableOfContents.php, все последовательные страницы отображаются соответствующим образом.

Мне кажется, что это проблема с указателем и областью видимости переменной, так что я могу сделать, чтобы это исправить?

Ответы [ 3 ]

3 голосов
/ 05 июля 2011

Диагностика

I подозреваю проблема в том, что $_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.
 */
0 голосов
/ 05 июля 2011

Решение состояло в том, чтобы избежать использования foreach и использовать обычные циклы, как предлагается здесь: вложенный foreach в задаче PHP

0 голосов
/ 05 июля 2011

Я не уверен, что понимаю, в чем ваша проблема, но вы можете посмотреть на функцию PHP reset =)

...