在自定义RecursiveIteratorIterator中对“(子类)实例未正确初始化”进行故障诊断

时间:2012-08-06 17:52:30

标签: php iterator doctrine-1.2 spl

我创建了一个扩展RecursiveIteratorIterator的自定义迭代器,用于从使用Doctrine_Collection行为的表中迭代NestedSet(例如,以便我将自定义排序应用于层次结构中每个级别的记录。)

我的项目中有几个模型利用了这个迭代器,所以我创建了一个如下所示的基类:

/** Base functionality for iterators designed to iterate over nested set
 *    structures.
 */
abstract class BaseHierarchyIterator
  extends RecursiveIteratorIterator
{
  /** Returns the component name that the iterator is designed to work with.
   *
   * @return string
   */
  abstract public function getComponentName(  );

  /** Inits the class instance.
   *
   * @param $objects  Doctrine_Collection Assumed to already be sorted by `lft`.
   *
   * @throws LogicException If $objects is a collection from the wrong table.
   */
  public function __construct( Doctrine_Collection $objects )
  {
    /** @kludge Initialization will fail horribly if we invoke a subclass method
     *    before we have initialized the inner iterator.
     */
    parent::__construct(new RecursiveArrayIterator(array()));

    /* Make sure we have the correct collection type. */
    $component = $this->getComponentName();
    if( $objects->getTable()->getComponentName() != $component )
    {
      throw new LogicException(sprintf(
        '%s can only iterate over %s collections.'
          , get_class($this)
          , $component
      ));
    }

    /* Build the array for the inner iterator. */
    $top = array();
    /** @var $object Doctrine_Record|Doctrine_Node_NestedSet */
    foreach( $objects as $object )
    {
      // ... magic happens here ...
    }

    parent::__construct(
        new RecursiveArrayIterator($top)
      , RecursiveIteratorIterator::SELF_FIRST
    );
  }

  ...
}

子类可能如下所示:

/** Iterates hierarchically through a collection of User objects.
 */
class UserHierarchyIterator
  extends BaseHierarchyIterator
{
  /** Returns the component name that the iterator is designed to work with.
   *
   * @return string
   */
  public function getComponentName()
  {
    return UserTable::getInstance()->getComponentName();
  }

  ...
}

请注意基类中构造函数顶部的@kludge

/** @kludge Initialization will fail horribly if we invoke a subclass method
 *    before we have initialized the inner iterator.
 */
parent::__construct(new RecursiveArrayIterator(array()));

只要我在基类的构造函数顶部保留额外的初始化行,一切都按预期工作。

但是,如果我删除/注释该行,一旦脚本执行到$component = $this->getComponentName(),我就会收到以下错误:

  

致命错误:BaseHierarchyIterator :: __ construct():UserHierarchyIterator实例未在第21行的/path/to/BaseHierarchyIterator.class.php中正确初始化。

或者,如果我删除调用$this->getComponentName()的代码(以及后续的条件块),构造函数仍然按预期运行(减去检查以确保组件名称正确)。

此错误的根本原因是什么?这个问题有更好的解决方法吗?

PHP版本信息:

PHP 5.3.3 (cli) (built: Jul  3 2012 16:40:30) 
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
    with Suhosin v0.9.29, Copyright (c) 2007, by SektionEins GmbH

2 个答案:

答案 0 :(得分:1)

  

致命错误:BaseHierarchyIterator :: __ construct():UserHierarchyIterator实例未在第21行的/path/to/BaseHierarchyIterator.class.php中正确初始化。

此错误的根本原因是什么?

您的班级来自RecursiveIteratorIterator。要使这种类型的对象或其子类型起作用,PHP需要在调用任何其他方法或访问其属性之前正确初始化它。恰当地意味着它已调用它的父构造函数,然后完成初始化。

但是在初始化之前调用方法(RecursiveIteratorIterator::__construct()尚未调用)。您的案例中的方法调用是:

$this->getComponentName();

到目前为止,你也已经意识到了。如果在该调用之前初始化,则不会出现此致命错误。在您的情况下,初始化是:

parent::__construct(new RecursiveArrayIterator(array()));

PHP的严格检查保留PHP做一些特殊的递归迭代器,它们可以更好地委托给底层数据结构。递归遍历也稍微复杂一些,需要初始化堆栈等等,这些都是PHP隐藏的。因此,出于安全原因也进行了检查。

如果将其与 IteratorIterator 进行比较,您会发现此类致命错误不存在。

此问题是否有更好的解决方法?

我不会把它称为workround。您实际上实现了与IteratorAggregate相似的类似内容。但是你只是立刻做了太多,看到你的构造函数,它做得太多了。这是Flaw: Constructor does Real Work

因此,这需要更清晰地分离关注点,实际上对迭代器的更基本理解也会有所帮助。

解决方案非常简单:您需要做的唯一事情就是将数据处理逻辑从构造函数移动到IteratorAggregate的实现中:

abstract class BaseHierarchyIterator implements IteratorAggregate
{
    private $objects;

    abstract public function getComponentName();

    public function __construct(Doctrine_Collection $objects) {
        $this->setObjects($objects);
    }

    public function getIterator() {

        /* Build the array for the inner iterator. */
        $top = array();
        /** @var $object Doctrine_Record|Doctrine_Node_NestedSet */
        foreach ($this->objects as $object) {
            // ... magic happens here ...
        }

        return new RecursiveArrayIterator($top);
    }

    private function setObjects($objects) {

        /* Make sure we have the correct collection type. */
        $component = $this->getComponentName();

        if ($objects->getTable()->getComponentName() != $component) {
            throw new LogicException(sprintf(
                '%s can only iterate over %s collections.'
                , get_class($this)
                , $component
            ));
        }

        $this->objects = $objects;
    }
}

然后你可以立即进行递归迭代:

$users = new UserHierarchyIterator($objects);
$it    = new RecursiveIteratorIterator(
                 $users, RecursiveIteratorIterator::SELF_FIRST
             );

正如您所看到的,解决具体问题所需的唯一方法就是将问题分开一些。无论如何这对你有所帮助,所以你应该在第一次运行中没问题:

[Iterator]  ---- performs traversal --->  [Container]

容器有一个迭代器现在可以使用的接口。顺便说一句,这正是迭代器对象。你实际上错了。如果您遵循该跟踪并且与IteratorAggregate一样,您可以从那里转到容器的真正自己的迭代器接口。现在,在创建迭代器对象之前,迭代doctrine集合

答案 1 :(得分:0)

http://www.php.net/manual/en/oop4.constructor.php

  

PHP不会从派生类的构造函数中自动调用基类的构造函数。您有责任在适当的时候将调用传播到上游构造函数。

也就是说,在扩展类时必须显式调用构造函数,否则PHP不知道如何初始化基类。

PHP可能会尝试使用NULL参数调用基类构造函数,但这几乎肯定是错误的并且会导致意外的结果(在这种情况下 - 你可能会得到一堆'参数X期望一个数组,NULL如果PHP决定尝试将NULL传递给基础构造函数而不是抛出错误,则给出“错误”