Что именно возвращает PHP SPL RecursiveIterator::getChildren()?

Я изучаю стандартную библиотеку PHP (SPL) Маркуса Бергера.

Я реализовал свой собственный RecursiveIterator, который по наследству реализует Iterator интерфейс. Это также реализует Countable,

Меня смущают методы current (), getChildren () и hasChildren. Это задокументировано по адресу: http://www.php.net/~helly/php/ext/spl/interfaceRecursiveIterator.html

Если

  • current() цитата: "Возвращает текущий элемент", и
  • getChildren() возвращает, и я цитирую "суб итератор для текущего элемента"

Если, как в случае с current()текущий элемент считается дочерним по отношению к текущему объекту.

Затем, безусловно, в документации указывается, что getChildren(), по сути, возвращает внуков рассматриваемого узла.

Следовательно, запутался.

<?php

/**
*@desc Represents a node within a hierarchy
*/
class RecursableCountableIterableNode implements RecursiveIterator, Countable
    {

    public $title;

    private $_componentsArray;
    private $_iteratorPosition;

    /**
    *@desc adds component
    */
    public function addComponent( 
            RecursableCountableIterableNode $incomingNodeObj 
            )
        {

        foreach ( $this->_componentsArray as $componentNodeObj )
            {
            if ( $incomingNodeObj === $componentNodeObj )
                {
                //its is already in there
                return;
                }
            }


        //add to the next element of the numerically indexed array
        $this->_componentsArray[] = $incomingNodeObj;       
        }



    /**
    * @desc RecursiveIterator Interface 
    */

    /**
    * @desc Implements the RecursiveIterator Interface 
    * @return boolean - Whether or not the node at the current element
    *  has children.
    * 
    * Note: This method does NOT count the children of this node, 
    * it counts the components of the node at the *current* element.
    * There is a subtle but important difference. 
    * It could have been better to name 
    * the interface method 'hasGrandChildren()'.
    */
    public function hasChildren()
        {
        return ( boolean ) count( $this->current() );
        }

    /**
    * @desc Gets the node of the current element which in effect is a container
    *  for childnodes. 
    * 
    * Note: According to the SPL, it does NOT get 'the child elements of
    *  the current node' ($this->_componentsArray) which was a surprise to me.
    * 
    * @return RecursableCountableIterableNode - the 
    * sub iterator for the current element 
    * 
    */
    public function getChildren()
        {
        return $this->current();
        }


    /**
    * @desc To adhere to countable interface.
    * @returns integer - The number of elements in the compondents array.
    */
    public function count()
        {
        return count( $this->_componentsArray );
        }


    /**
    * Iterator methods
    */

    /**
    * @desc Rewind the iterator to the first element.
    * @return void
    */
    public function rewind()
        {
        $this->_iteratorPosition = 0;
        }

    /**
    * @desc Return the current element.
    * @return RecursableCountableIterableNode
    */
    public function current()
        {
        return $this->_componentsArray[ $this->_iteratorPosition ];
        }

    /**
    * @desc Return the key of the current element.
    * @return integer
    */
    public function key()
        {
        return $this->_iteratorPosition;
        }

    /**
    * @desc Move forward to the next element.
    * @return void
    */
    public function next()
        {
        ++$this->_iteratorPosition;
        }

    /**
    * @desc Checks if current position has an element
    * @return boolean
    */
    public function valid()
        {
        return isset( $this->_componentsArray[ $this->_iteratorPosition ] );
        }   

    }

В классе выше, getChildren() возвращает объект, который реализует RecursiveIterator и Countable. Потому что каждый RecursableCountableIterableNode объект содержит экземпляры других RecursableCountableIterableNode объекты. Я думаю, что это форма Composite Pattern.

Благодаря экспериментам мне удалось выполнить рекурсивную операцию с деревом, используя count() (в качестве терминального условия для выхода из рекурсивного процесса) и foreach перебирать детей каждого узла.

Что интересно, так это то, что count особенность простоты делает hasChildren операция и foreach конструкция неявно делает getChildren операция для выполнения рекурсивного обхода.

class NodeTreeProcessor
    {
    protected $output = '';

    public function doProcessingWithNode( 
            RecursableCountableIterableNode $treeNodeObj
            )
        {

        $this->output .= $treeNodeObj->title;

        //Base case that stops the recursion.
        if (!( count( $treeNodeObj ) > 0 ))
            {
            //it has no children
            return;
            }

        //Recursive case.
        foreach( $treeNodeObj as $childNode )
            {
            $this->doProcessingWithNode( $childNode );
            }       
        }
    }

Учитывая это, я думаю, что для того, чтобы быть практическим RecursiveIterator,

  • getChildren действительно должен вернуться $this вместо узла в current(), а также
  • hasChildren действительно должен вернуть результат логического броска count($this)

это правильно?

Спецификации говорят об одном - что я понимаю буквально. Но мой практический опыт говорит другое.

1 ответ

Я не считаю правильным говорить "внуки". Вы просто меняете свою контрольную точку с элемента текущего итератора на текущий итератор, который превращает детей в внуков. Я не вижу веской причины для этого, потому что это просто не тот подход, к которому я привык с итераторами spl.

Я рекомендую вам придерживаться кода, подобного тому, который вы опубликовали, но я думаю, что вы, возможно, не знаете о RecursiveIteratorIterator. RecursiveIteratorIterator предназначен для того, чтобы справляться со сложностью вызова hasChildren() и getChildren() и поддерживать надлежащий стек итераторов в процессе. В конце концов, вам представляется то, что выглядит как плоский список вашей иерархии. Ваш класс NodeTreeProcessor в настоящее время делает некоторые из этих вещей. Затем вы можете просто перейти к RecursiveIteratorIterator, и вы получите ширину первой или глубины первой итерации в зависимости от того, какие флаги вы использовали. Вам не нужно использовать RecursiveIteratorIterator, хотя.

Также рассмотрите возможность возврата нового внешнего итератора при вызове getChildren(). В противном случае вы отказываетесь от возможности итерации по одному и тому же узлу с более чем одним итератором за раз, потому что позиция вашего итератора будет общим состоянием. В настоящее время вы используете внутреннюю парадигму итератора, в которой и данные, и состояние итерации хранятся в одном и том же объекте. Внешний итератор отсоединяет состояние итерации от данных, позволяя иметь более одного активного итератора на один и тот же фрагмент данных.

Другие вопросы по тегам