Jonas Schreiber
Jonas Schreiber

Reputation: 457

PHP Recursion: Flatten Tree, Preserve Metadata

Imagine you have a tree representing a family that you want to persist to tables, but preserve nesting levels. I'd like to get the post data below into the structure underneath.

I believe a RecursiveIterator might be the way to do this, but I am not sure how I would do so. I have code that works for most cases, but it has become an ugly, bloated thing. I can post that if you'd like.

stdClass Object
(
    [name] => Smith
    [type] => Family
    [children] => Array(
            [1] => stdClass Object
                (
                    [name] => Michael
                    [type] => Uncle
                    [children] => Array(
                            [0] => stdClass Object
                                (
                                    [name] => Jared
                                    [type] => cousin
                                )
                        )
                )
            [2] => stdClass Object
                (
                    [name] => Jeff
                    [type] => Dad
                    [children] => Array(
                            [0] => stdClass Object
                                (
                                    [name] => Jonas
                                    [type] => self
                                )
                            [1] => stdClass Object
                                (
                                    [name] => Leah
                                    [type] => sister
                                    [children] => Array(
                                            [0] => stdClass Object
                                                (
                                                    [name] => Jacob
                                                    [type] => nephew
                                                )
                                        )
                                )
                        )
                )
        )
)

The records for persistence should look like this:

Array
(
    stdClass Object ( [name] => Smith [type] => Family [subgroup] => 0 [parent_subgroup] => )
    stdClass Object ( [name] => Michael [type] => Uncle [subgroup] => 1 [parent_subgroup] => 0 )
    stdClass Object ( [name] => Jared [type] => Cousin [subgroup] => 2 [parent_subgroup] => 1 )
    stdClass Object ( [name] => Jeff [type] => Dad [subgroup] => 1 [parent_subgroup] => 0 )
    stdClass Object ( [name] => Jonas [type] => self [subgroup] => 3 [parent_subgroup] => 1 )
    stdClass Object ( [name] => Leah [type] => sister [subgroup] => 3 [parent_subgroup] => 1 )
    stdClass Object ( [name] => Jacob [type] => nephew [subgroup] => 4 [parent_subgroup] => 3 )     
)

P.S. No, me and my sister didn't have a kid. That was just my analogy falling on its face. ;)

Upvotes: 3

Views: 205

Answers (1)

gen_Eric
gen_Eric

Reputation: 227260

RecursiveIterator classes can get a bit messy, I like to try to keep them simple. You can use RecursiveIteratorIterator to loop over the values of your iterator, it can even give you the current depth (or subgroup in your case).

The challenge here is that the parent isn't an array, but we can take care of that in the constructor.

<?php
class FamilyIterator implements RecursiveIterator{
    private $data, $counter;

    public function __construct($familyTree){
        $this->data = is_array($familyTree) ? $familyTree : [$familyTree];
    }

    public function current(){
        $row = $this->data[$this->counter];
        return (object)[
            'name' => $row->name,
            'type' => $row->type
        ];
    }

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

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

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

    public function valid(){
        return $this->counter < count($this->data);
    }

    public function hasChildren(){
        $row = $this->data[$this->counter];
        return isset($row->children);
    }

    public function getChildren(){
        $row = $this->data[$this->counter];
        return new self($row->children);
    }
}

Then you can use this class like:

$loop = new RecursiveIteratorIterator(
    new FamilyIterator($dataObj),
    RecursiveIteratorIterator::SELF_FIRST
);

When you foreach over $loop, it'll automatically call the getChildren method when it needs, so in the foreach you'll have each row. You can even ask RecursiveIteratorIterator for the depth.

$newData = [];
foreach($loop as $row){
    $row->subgroup = $loop->getDepth();
    $newData[] = $row;
}

DEMO: https://eval.in/444078

This may not be exactly what you wanted, but hopefully it'll point you in the right direction. RecursiveIterators don't need to be complicated.

Upvotes: 3

Related Questions