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

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

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

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

If

  • current() quote: 'Возвращает текущий элемент' и
  • 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. Я думаю, что это форма композитного шаблона.

Путем экспериментов мне удалось выполнить рекурсивную операцию на дереве, используя 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)

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

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


person JW.    schedule 03.07.2013    source источник
comment
getChildren должен возвращать current(), завернутый в другой итератор. Если у него есть дети   -  person tlenss    schedule 03.07.2013
comment
Спасибо за ваш комментарий. Он возвращает current(), но не завернутый, как вы говорите. Я должен был уточнить, что getChildren() действительно возвращает объект, который реализует RecursiveIterator и Countable. Каждый объект RecursableCountableIterableNode содержит экземпляры других объектов RecursableCountableIterableNode. Я думаю, что это форма композитного шаблона. Я отредактирую вопрос, чтобы уточнить.   -  person JW.    schedule 03.07.2013
comment
@tlenss В дополнение к моему комментарию выше, есть ли причина, по которой его следует завернуть в другой итератор?   -  person JW.    schedule 03.07.2013
comment
Таким образом, вы можете перебирать их настоящим рекурсивным способом.   -  person tlenss    schedule 03.07.2013


Ответы (1)


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

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

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

person goat    schedule 06.07.2013
comment
Спасибо за ответ. Очень полезная информация. - person JW.; 06.07.2013