Question

I'm having difficulty to get RecursiveFilterIterator to visit children of objects that I don't want to accept, without also returning the unacceptable object.

I have two types of Node objects: NodeTypeA and NodeTypeA, that both extend abstract type Node:

abstract class Node implements Countable, RecursiveIterator
{
    protected $children;

    public function __construct( array $children = array() )
    {
        $this->children = $children;
    }

    public function count()
    {
        return count( $this->children );
    }

    public function hasChildren()
    {
        if( !$this->valid() )
        {
            return false;
        }

        return count( $this->current() ) > 0;
    }

    public function getChildren()
    {
        return $this->current();
    }

    public function rewind()
    {
        reset( $this->children );
    }

    public function key()
    {
        return key( $this->children );
    }

    public function valid()
    {
        return null !== $this->key();
    }

    public function current()
    {
        return current( $this->children );
    }

    public function next()
    {
        next( $this->children );
    }
}

class NodeTypeA extends Node {}

class NodeTypeB extends Node {}

... for which I defined this RecursiveFilterIterator:

class RecursiveNodeFilterIterator
    extends RecursiveFilterIterator
{
    public function __construct( RecursiveIterator $iterator, $kind )
    {
        parent::__construct( $iterator );

        $this->kind = $kind;
    }

    public function accept()
    {
        $current = $this->current();
        return $this->hasChildren() || $current instanceof $this->kind;
    }

    public function getChildren()
    {
        return new self( $this->getInnerIterator()->getChildren(), $this->kind );
    }
}

Then, when I run this snippet:

header( 'Content-Type: text/plain; charset=utf-8' );

$nodes = new NodeTypeA( array(
    new NodeTypeB( array(
        new NodeTypeA( array(
            new NodeTypeB( array(
                new NodeTypeA(),
                new NodeTypeA()
            ) ),
        ) ),
        new NodeTypeA( array(
            new NodeTypeB( array(
                new NodeTypeB( array(
                    new NodeTypeA(),
                    new NodeTypeB()
                ) ),
            ) )
        ) ),
        new NodeTypeB()
    ) ),
    new NodeTypeA()
) );

$rii = new RecursiveIteratorIterator(
    new RecursiveNodeFilterIterator( $nodes, 'NodeTypeA' ),
    RecursiveIteratorIterator::SELF_FIRST
);

foreach( $rii as $node )
{
   echo str_repeat( '  ', $rii->getDepth() ) . get_class( $node ) . PHP_EOL;
}

I was hoping to get this result:

  NodeTypeA
      NodeTypeA
      NodeTypeA
  NodeTypeA
        NodeTypeA
NodeTypeA

... but got:

NodeTypeB
  NodeTypeA
    NodeTypeB
      NodeTypeA
      NodeTypeA
  NodeTypeA
    NodeTypeB
      NodeTypeB
        NodeTypeA
NodeTypeA

In other words, on iterating, it also returns NodeTypeB objects when they have children. This makes sense, since in RecursiveNodeFilterIterator::accept() I have defined return $this->hasChildren() || ..., in order for the filter to visit all possible child nodes.

Is there a way to have RecursiveNodeFilterIterator visit child nodes of NodeTypeB without actually returning the NodeTypeB nodes themselves?

Was it helpful?

Solution

A simple option is to wrap the RecursiveIteratorIterator with a filter iterator that only accepts the items that you are interested in.

(Moved from comment to answer)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top