2

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

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

/** 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)(构建:2012 年 7 月 3 日 16:40:30)
版权所有 (c) 1997-2010 PHP 集团
Zend Engine v2.3.0,版权所有 (c) 1998-2010 Zend Technologies
    与 Suhosin v0.9.29,版权所有 (c) 2007,SektionEins GmbH
4

2 回答 2

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进行比较,您会发现不存在这样的致命错误。

这个问题有更好的解决方法吗?

我不会称之为工作区。您实际上实现了类似的类似 IteratorAggregate功能。但是你一次做的太多了,看看你的构造函数,它做的太多了。这是一个缺陷:构造函数做真正的工作

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

解决方案相当简单:您在这里唯一需要做的就是将数据处理逻辑从构造函数移动到以下实现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如果您遵循该路径并且与. 现在,您在创建迭代器对象之前迭代了学说集合。

于 2012-10-12T13:29:17.860 回答
0

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

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

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

PHP 可以尝试使用 NULL 参数调用基类构造函数,但这几乎肯定是错误的并且会导致意想不到的结果(在这种情况下 - 你可能会得到一堆 'param X expected an array, NULL given' 错误如果 PHP 决定尝试将 NULL 传递给基本构造函数而不是抛出错误)

于 2012-08-28T19:27:40.763 回答