Baumstruktur Iterator-Tag

Im Entwickler-Forum können Implementierungsdetails sowie Alternativen der Umsetzung diskutiert werden. // Here, developers can discuss implementation details of features of their projects.
Antworten
Thalo
Beiträge: 240
Registriert: 10.08.2009, 16:56:52

Baumstruktur Iterator-Tag

Beitrag von Thalo » 30.10.2015, 01:58:19

Hallo zusammen :),

lässt sich mit dem Iterator-Tag auch ein Baum in einer flachen Struktur darstellen? Als Beispiel mit dem GORM:

Code: Alles auswählen

function printExample ($node, $parentNodeNames = []) {
    if (!empty($parentNodeNames)) {
        echo implode(' / ', $parentNodeNames) . ' / ';
    }

    echo $node->getProperty('Name');

    $parentNodeNames[] = $node->getProperty('Name');
    $children = $node->getChildren();

    foreach ($children as $child) {
        printExample($child, $parentNodeNames);
    }
}

foreach ($ORM->loadObjectTree('NavigationNode', 'NavigationNode2NavigationNode') as $node) {
    printExample($node);
}
Ausgabe:

Code: Alles auswählen

NODE-1
NODE-2
NODE-2 / NODE-2-1
NODE-2 / NODE-2-2
NODE-2 / NODE-2-2 / NODE-2-2-1
NODE-3
NODE-3 / NODE-3-1
NODE-4
Geht das auch mit dem Iterator-Tag oder brauchts da einen eigenen Tag?

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Baumstruktur Iterator-Tag

Beitrag von dr.e. » 30.10.2015, 23:52:41

Hallo Thalo,

ich denke es wird nicht out-of-the-box funktionieren, da der Iterator eine Liste mit dedizierter Tiefe erwartet - in der Regel 1 oder 2 Ebenen. Du kannst allerdings den Iterator nutzen um jeweils die aktuelle Ebene darzustellen. Dazu ist dann ein wenig Code um die Iterator-Ausgabe notwendig um die Rekursion zu implementieren bzw. das Einrücken der Liste zu erzeugen. Stichwort: "<ul>" in "<li>". In der Rekursion ist es dann wichtig, dass du den Level kennst, in dem du dich befindest um die Einrückung korrekt zu berechnen.

Das sollte mit deiner Funktion als Grundlage umsetzbar sein.

Hoffe das beantwortet deine Frage! :)
Viele Grüße,
Christian

Thalo
Beiträge: 240
Registriert: 10.08.2009, 16:56:52

Re: Baumstruktur Iterator-Tag

Beitrag von Thalo » 31.10.2015, 02:30:03

Hi Doc,

hast du da vielleicht ein Beispiel für mich? :geek: Ähnlich ist es ja hier in der Doku, aber eben als Baum. Das Problem ist ja die „Rückreferenzierung“ auf die Namen der Parent-Nodes z.B.:

Code: Alles auswählen

NODE-2 / NODE-2-1
NODE-2 / NODE-2-2
NODE-2 / NODE-2-2 / NODE-2-2-1
Edit: Erst jetzt gesehen, dass man beim TreeItem auch auf den Parent über getParentItem zugreifen kann. Das würde sich ja dann eleganter implementieren lassen.

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Baumstruktur Iterator-Tag

Beitrag von dr.e. » 02.11.2015, 16:14:39

Hallo Thalo,

aktuell habe ich leider kein Beispiel zur Hand. Ich habe mir allerdings den Iterator angesehen und ich denke, dass sich dieser (leider wie vermutet) nur für eine Ebene nutzen lässt. Die Rekursion wirst du im Controller abbilden müssen. Grund: die Verschachtelung ist statisch hinsichtlich der Tiefe des Baums, da du die Verschactelung im Template definierst.

Wenn du magst, versuche sich mal an der Implementierung und wir helfen wo du Hilfe brauchst. Einverstanden?
Viele Grüße,
Christian

Thalo
Beiträge: 240
Registriert: 10.08.2009, 16:56:52

Re: Baumstruktur Iterator-Tag

Beitrag von Thalo » 04.11.2015, 19:05:23

Hi Doc,

wie wäre es den Iterator-Tag dahingehend zu adaptieren, dass er die SPL-Iteratoren unterstützt (z.B. konsequentes Einsetzen von foreach)? Dann könnte man statt arrays auch die SPL Iteratoren nutzen bzw. Eigenimplementierungen die die Iterator-Interfaces nutzen. Damit wäre auch die Möglichkeit von Rekursionen geschaffen. :)

Einzig wie man meinen Anwendungsfall generisch genug in einen Tag abbildet ist mir noch nicht ganz klar. :geek:

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Baumstruktur Iterator-Tag

Beitrag von dr.e. » 04.11.2015, 19:22:24

Hi Thalo,

wenn du magst, schreibe mal im Tracker einen Optimierungsvorschlag für den Iterator zusammen. Diesen können wir dann gerne für 3.2 implementieren.

Was die generische Abbildung angeht, so brauchst du für eine funktionierende Rekursion Sub-Elemente für die verschiedenen Entscheidungspunkte: ist ein Knoten markiert (eher für Navigation an relevant), hat ein Knoten Kinder oder hat er keine. Für diese plus für einen Eintrag selbst brauchst du eine Repräsentation wie sie im Iterator mit dem <Iterator:item/> vorhanden ist. Diese kannst du dir dann im Tag ziehen um die Ausgabe zu generieren. Wichtig ist vor allem das unterschiedliche Markup für Knoten mit Kinder. Hast du das unterschieden sollte die Implementierung funktionieren.
Viele Grüße,
Christian

Thalo
Beiträge: 240
Registriert: 10.08.2009, 16:56:52

Re: Baumstruktur Iterator-Tag

Beitrag von Thalo » 04.11.2015, 21:40:55

Hi Doc,

ich habe mal einen POC zusammen gehackt. Um die Vorzüge der SPLIteratoren auszunutzen habe ich einen eigenen Iterator implementiert:

Code: Alles auswählen

class Node {
        private $name = '';
        private $children = [];

        public function getName() {
                return $this->name;
        }

        public function setName($name) {
                $this->name = $name;
        }

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

        public function addChild($node) {
                $this->children[] = $node;
        }

       public function __toString() {
               return $this->name;
       }
}

Code: Alles auswählen

class RecursiveNodeIterator implements RecursiveIterator {
        private $data;

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

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

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

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

        public function valid() {
                return current($this->data) !== false;
        }

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

        public function hasChildren() {
                return count(current($this->data)->getChildren()) > 0;
        }

        public function getChildren() {
                return new RecursiveNodeIterator(current($this->data)->getChildren());
        }
}
Mein Beispiel könnte man dann so implementieren:

Code: Alles auswählen

$node1 = new Node();
$node1->setName('NODE-1');

$node2 = new Node();
$node2->setName('NODE-2');

$node21 = new Node();
$node21->setName('NODE-2-1');

$node22 = new Node();
$node22->setName('NODE-2-2');

$node221 = new Node();
$node221->setName('NODE-2-2-1');

$node3 = new Node();
$node3->setName('NODE-3');

$node31 = new Node();
$node31->setName('NODE-3-1');

$node4 = new Node();
$node4->setName('NODE-4');

$node2->addChild($node21);
$node2->addChild($node22);

$node22->addChild($node221);

$node3->addChild($node31);

$nodes = [];
$nodes[] = $node1;
$nodes[] = $node2;
$nodes[] = $node3;
$nodes[] = $node4;

$iterator = new RecursiveIteratorIterator(new RecursiveNodeIterator($nodes), RecursiveIteratorIterator::SELF_FIRST);

foreach($iterator as $node) {
        // durch die parents iterieren
        for($i = 0; $i < $iterator->getDepth(); $i++) {
                echo $iterator->getSubIterator($i)->current()->getName().' / ';
        }
        echo $node->getName().'<br>';
}
Ergebnis:

Code: Alles auswählen

NODE-1
NODE-2
NODE-2 / NODE-2-1
NODE-2 / NODE-2-2
NODE-2 / NODE-2-2 / NODE-2-2-1
NODE-3
NODE-3 / NODE-3-1
NODE-4
das würde auch OOTB mit dem GORM-Tree funktionieren. Anstatt das Markup immer wieder neu für einen Tree-View schreiben zu müssen könnte man es in einen eigenen Iterator auslagern (so wie hier). Nachteil wäre, dass das Markup nicht zentral im Template liegt (quick 'n' dirty):

Code: Alles auswählen

class RecursiveHtmlTreeIterator extends RecursiveIteratorIterator implements OuterIterator {
        public function beginIteration() {
                echo '<ul>';
        }

        public function endIteration() {
                echo '</ul>';
        }

        public function beginChildren() {
                echo '<ul>';
        }

        public function endChildren() {
                echo '</ul>';
        }

        public function current() {
                return '<li>'.parent::current().'</li>';
        }
}

Code: Alles auswählen

$recursiveNodeIterator = new RecursiveNodeIterator($nodes);
$iterator = new RecursiveHtmlTreeIterator($recursiveNodeIterator, RecursiveIteratorIterator::SELF_FIRST);

foreach($iterator as $node) {
        echo $node;
}
Ergebnis:

Code: Alles auswählen

<ul>
<li>NODE-1</li>
<li>NODE-2</li>
<ul>
<li>NODE-2-1</li>
<li>NODE-2-2</li>
<ul>
<li>NODE-2-2-1</li>
</ul>
</ul>
<li>NODE-3</li>
<ul>
<li>NODE-3-1</li>
</ul>
<li>NODE-4</li>
</ul>
Was die generische Abbildung angeht, so brauchst du für eine funktionierende Rekursion Sub-Elemente für die verschiedenen Entscheidungspunkte: ist ein Knoten markiert (eher für Navigation an relevant)
Wie soll sowas generisch möglich sein? Die Entscheidung müsste doch z.B. mit dem StatusModel im Controller getroffen werden. Das ist doch nicht möglich ohne die Iteration im Controller selbst zu implementieren?
Wichtig ist vor allem das unterschiedliche Markup für Knoten mit Kinder.
Würdest du das über einen eigenen Tag oder so wie in meinem Beispiel mit einem Iterator lösen? Evtl. könnte man ähnlich dem Iterator das StatusModel um "beginChildren" etc. erweitern.
Hast du das unterschieden sollte die Implementierung funktionieren.
Schau dir noch mal meine Anforderung an. Ich muss den Parent „rückreferenzieren“. Das wäre auch dann nicht möglich. Das müsste irgendwie im Controller gelöst werden. Nur wie? Mit der SPLIterartor-Methode wäre das sehr flexibel. :)

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Baumstruktur Iterator-Tag

Beitrag von dr.e. » 05.11.2015, 13:42:51

Hallo Thalo,

klingt nicht schlecht!
Nachteil wäre, dass das Markup nicht zentral im Template liegt
Sehe ich auch so.
Wie soll sowas generisch möglich sein? Die Entscheidung müsste doch z.B. mit dem StatusModel im Controller getroffen werden. Das ist doch nicht möglich ohne die Iteration im Controller selbst zu implementieren?
Enweder im Iterator selbst oder im Controller an Hand den Eigenschaften deiner "Node". Dann bist du natürlich im Controller "gefangen" und kannst so ohne weiteres die SPL-Iteratoren nicht nutzen - das hast du Recht.
Würdest du das über einen eigenen Tag oder so wie in meinem Beispiel mit einem Iterator lösen? Evtl. könnte man ähnlich dem Iterator das StatusModel um "beginChildren" etc. erweitern.
Wenn du deinen Iterator so implementieren möchtest wie oben, dann würde ich den Weg gehen dein aktuelles Dokument zusätzlich an den Iterator zu übergeben und dann innerhalb von beginChildren() auf ein dort vorhandenes Templates o.ä. zugreifen.
Schau dir noch mal meine Anforderung an. Ich muss den Parent „rückreferenzieren“. Das wäre auch dann nicht möglich. Das müsste irgendwie im Controller gelöst werden. Nur wie? Mit der SPLIterartor-Methode wäre das sehr flexibel. :)
Siehe oben. Die SPL-Implementierung zu nutzen kannst du dann ja mit dem Template verbinden in dem du z.B. dein Template im Konstruktor des RecursiveHtmlTreeIterator übergibst.
Viele Grüße,
Christian

Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast