我创建了一个扩展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
答案 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传递给基础构造函数而不是抛出错误,则给出“错误”