Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP RecursiveIterator traversing

I have a structure representing a form and I want to iterate it using RecursiveIterator. The problem is this only returns the top-level questions. What am I doing wrong?

Whole form:

class Form implements RecursiveIterator{
    private $id;
    private $caption;
    private $other_text;
    private $questions = array();
    private $current;

    private function __construct(DibiRow $row){
        $this->id = $row->id;
        $this->caption = $row->caption;
        $this->other_text = $row->other_text;
        $this->loadQuestions();
    }

    private function loadQuestions(){
        $questions = dibi::query('SELECT * FROM cyp_questions WHERE form_id = %i AND parent_id IS NULL', $this->id);
        while($question = $questions->fetch()) $this->questions[] = new Question($question->question_id, $question->type, $question->caption, $question->other_text, $question->triggers_unique == 1);
    }

    /**
     * @throws InvalidArgumentException
     * @param $id
     * @return Form
     */
    public static function loadById($id){
        $form = dibi::query('SELECT * FROM cyp_forms WHERE id = %i', $id)->fetch();
        if($form === false) throw new InvalidArgumentException('Form with id '.$id.' was not found.');

        return new Form($form);
    }

    /**
     * @throws FormFieldException
     * @return bool
     */
    public function validate($postfields){

    }

    public function getQuestions(){
        return $this->questions;
    }

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

    public function hasChildren(){
        return count($this->questions) > 0;
    }

    public function current(){
        return $this->questions[$this->current];
    }

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

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

    public function rewind(){
        $this->current = 0;
    }

    public function valid(){
        return isset($this->questions[$this->current]);
    }
}

Question:

class Question implements RecursiveIterator{
    private $id;
    private $type;
    private $answers = array();
    private $subquestions = array();
    private $other_text;
    private $triggers_unique;
    private $caption;
    private $current = 0;


    public function __construct($id, $type, $caption, $other_text = null, $triggers_unique = false){
        $this->id = $id;
        $this->type = $type;
        $this->caption = $caption;
        $this->other_text = $other_text;
        $this->triggers_unique = $triggers_unique;  
        $this->setSubQuestions();   

    }

    private function setSubQuestions(){
        $questions = dibi::query('SELECT * FROM cyp_questions WHERE parent_id = %i', $this->id);
        while($question = $questions->fetch()) $this->subquestions[] = new Question($question->question_id, $question->type, $question->caption, $question->other_text, $question->triggers_unique == 1);
    }

    public function getOtherText(){
        return $this->other_text;
    }

    public function getCaption(){
        return $this->caption;
    }

    public function addAnswer($answer){
        $this->answers[] = $answer;
    }

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

    public function hasChildren(){
        return count($this->subquestions) > 0;
    }

    public function current(){
        return $this->subquestions[$this->current];
    }

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

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

    public function rewind(){
        $this->current = 0; 
    }

    public function valid(){
        return isset($this->subquestions[$this->current]);
    }

    public function getAnswers(){
        return $this->answers;
    }


}

Iteration:

$form = Form::loadById(1);

foreach($form as $question){
    echo $question->getCaption().'<br />';  
}
like image 580
cypher Avatar asked Nov 12 '10 12:11

cypher


2 Answers

To iterate over a RecursiveIterator, you have to wrap it into a RecursiveIteratorIterator.

See some examples at

  • Introduction to Spl
  • SplWiki

The default iteration mode is only to list leaves. If you also want the containing nodes to appear in the iteration, pass RecursiveIteratorIterator::SELF_FIRST as the second argument to the constructor of the RecursiveIteratorIterator

like image 67
Gordon Avatar answered Oct 17 '22 23:10

Gordon


Well, as you can see here

public RecursiveIterator RecursiveIterator::getChildren ( void )

Returns an iterator for the current iterator entry. 

the method should return an object implementing the iterator. Your method return a simple array.

My guess would be to return something like:

public function getChildren(){
    return new Question($this->subquestions);
}

This is because you're using a RECURSIVE iterator so it's expected to have each node of the tree of the same type (an iterator)

like image 2
dierre Avatar answered Oct 17 '22 22:10

dierre