2

動作を使用するテーブルからRecursiveIteratorIterator反復するために使用するカスタム イテレータを作成しました(たとえば、階層内の各レベルのレコードにカスタム ソートを適用するため)。Doctrine_CollectionNestedSet

私のプロジェクトには、この反復子を利用するモデルがいくつかあるため、次のような基本クラスを作成しました。

/** 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)
Copyright (c) 1997-2010 PHP グループ
Zend Engine v2.3.0、Copyright (c) 1998-2010 Zend Technologies
    Suhosin v0.9.29、Copyright (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しました。しかし、一度にやりすぎました。コンストラクターを確認してください。やりすぎです。これは欠陥です: コンストラクターは 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]

コンテナーには、イテレーターが現在作業できるインターフェースがあります。ちなみに、これはまさに iterator オブジェクトです。あなたは実際にそれを間違って使用しました。その道をたどると、それは と共通しているIteratorAggregateため、そこからコンテナ用の真の独自のイテレータ インターフェイスに進むことができます。現在、反復子オブジェクトを作成するに教義コレクションを繰り返し処理しています。

于 2012-10-12T13:29:17.860 に答える
0

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

PHPは、派生クラスのコンストラクターから基本クラスのコンストラクターを自動的に呼び出すことはありません。必要に応じて、呼び出しを上流のコンストラクターに伝播するのはユーザーの責任です。

つまり、クラスを拡張するときにコンストラクターを明示的に呼び出す必要があります。そうしないと、PHPは基本クラスを初期化する方法を知りません。

PHPは、NULLパラメーターを使用して基本クラスコンストラクターを呼び出そうとする可能性がありますが、それはほぼ間違いなく間違っており、予期しない結果につながる可能性があります(この場合、「パラメーターXは配列を期待し、NULLが与えられた」エラーが発生する可能性があります) PHPがエラーをスローする代わりにベースコンストラクターにNULLを渡そうと決定した場合)

于 2012-08-28T19:27:40.763 に答える