======[PHP] AdjacencyTree Class====== Eine Klasse um einen Adjacency-Tree in PHP aufzulösen und sauber auszugeben. Das Ausgabeformat ist anpassbar. =====Ausgangslage===== Ich habe bereits im %%MySQL%% gezeigt, wie man einen solchen Tree auslesen kann: [[:mysql:adjacencytree:]]. Hier nun ein Beispiel für PHP. Der Vorteil dieser Lösung gegenüber des %%MySQL%%-Lösung ist sicher, dass sie viel einfacher Anwendbar und Änderbar ist. Hintergrund ist eine Anfrage von suntrop im Tutorial-Froum: [[http://www.tutorials.de/php/392167-formular-select-mit-hierarchie-aufbauen-aus-php-array.html|Formular-Select mit Hierarchie aufbauen (aus PHP-Array)]]. Auf diese Frage hatte ich den Codeschnipsel [[:php:kompost:adjacencytree]] erstellt. Doch um eine fertig brauchbare Lösung zu haben bin ich wieder mal auf die gute OOP-Methodik gekommen. Hier das Resultat. Ich habe die folgende Tabelle als Datengrundlage. Die Daten sind nicht in der richtigen Reihenfolge für den Tree. ^ Erklärung der Felder von tbl_adjacency_tree ^^ ^ id | Die ID des Eintrages | ^ parent_id | die Id des Vaters | ^ title | Der Titel der Eintrages | ^ tooltip | Ein Tooltip der angezeigt werden sool. Entspricht dem 'title'-Feld im Link-Tag | ^ order | Die Reihenfolge. Wird verwendet um die Nodes innerhalb des gleichen Levels zu sortieren | ^ value | Das Ziel des Linkes | ^ tbl_adjacency_tree^^^^^^ ^ id* ^ parent_id* ^ title* ^ tooltip ^ order (int) ^ value ^ | bar1 | foo | bar 1 | | | bar | | bar2 | foo | bar 2 | noch ein bar | 2 | bar | | foo | home | Foo | | | foo | | home | root | Home | zurück zum Anfang | 1 | | | links | root | Links | meine Links | 2 | | | 45 | links | tutorials | | 2 | http://www.tutorials.de | | 1 | links | yaslaw´s tolle Seite | gehe zu los, ohne.. | 1 | http://yaslaw.info | Die gewünschte Struktur ist wie folgt: [id] title [home] Home - [foo] Foo -- [bar1] bar 1 -- [bar2] bar 2 [links] Links - [1] yaslaw´s tolle Seite - [45] tutorials Die Idee meiner Umsetzung ist es, eine Klasse für den Tree zu haben und eine Klasse für die Nodes. Die Node-Klasse handhabt die Informateionen pro Node und deren Ausgabe. Um eine andere Ausgabe als die Standartausgabe zu erstellen kann man einfach die Node-Klasse ableiten und entsprechend die Ausgabe überschreiben. =====Anwendungsbeispiele===== ====Beispiel 1) Einfache Grundanwendung==== Ganz einfach den Tree mit allen Standarteinstellungen ausgeben === Code=== setRootKey('root'); //Alternativ: //$tree = new AdjacencyTree(AdjacencyTree::DEFAULT_NODE_CLASS, 'root') //Die Daten aus der DB laden $sql = <<createNode($row['id'], $row['parent_id'], $row['title']); } //Tree berechnen $tree->calcTree(); //und ausgeben. foreach($tree as $node){ echo "{$node->treeText}
\n"; } ?>
===Erstellte HTML-Source=== Home
-- Foo
-- -- bar 1
-- -- bar 2
Links
-- tutorials
-- yaslaw´s tolle Seite
===Erstellte HTML-Ansicht=== Home
-- Foo
-- -- bar 1
-- -- bar 2
Links
-- tutorials
-- yaslaw´s tolle Seite
===Alternativ die Erstellung der Nodes über die Node-Class=== //Die Nodes erstelle ich hier mal mit dem Constructeur von AdjacencyTreeNode while($row = mysql_fetch_assoc($result)){ $tree->append(new AdjacencyTreeNode(array('id'=>$row['id'], 'parentId'=>$row['parent_id'], 'title'=>$row['title']))); } //Die Nodes erstelle ich hier mal mit der createNode-Funktion von AdjacencyTreeNode while($row = mysql_fetch_assoc($result)){ $tree->append(AdjacencyTreeNode::create($row['id'], $row['parent_id'], $row['title'])); } ====Beispiel 2) Mit eigener Sortierung und erweiterten Properties==== In diesem Beispiel will ich zeigen, wie man mit einer eigenen Node-Klasse (in dem Fall eine Ableitung des AdjacencyTreeNode) eigene Sortierungen und andere TreeTexte setzen kann. Natürlich kann man auch eine komplett andere Node-Klasse schreiben, sie muss sich einfach an das IAdjacencyTreeNode halten. ===Code=== treeText = '• ' . str_repeat('→ ', $this->level) . $this->title; } /** * Sortierung innerhalb der Gruppen anhand der Parameter 'order' und 'title' * @param String $sortParent Sortierung des ParentNodes * @see AdjacencyTreeNode::setSortText() */ protected function setSortText($sortParent){ $this->sort = sprintf('%s.[%04d_%s]', $sortParent, $this->order, $this->title); } } //TreeObject erstellen $tree = new AdjacencyTree('AdjacencyTreeNode1', 'root'); //Die Daten aus der DB laden $sql = <<prepare($sql); $stmt->execute(); //und als AdjacencyTreeNode1-Objekte dem TreeObject übergeben $tree->exchangeArray($stmt->fetchAll(PDO::FETCH_CLASS, 'AdjacencyTreeNode1')); //Tree berechnen $tree->calcTree(); //und ausgeben. In diesem Fall als Link foreach($tree as $node){ echo sprintf("%s
\n", $node->value, $node->tooltip, $node->treeText); } ?>
===Erstellte HTML-Source=== • Home
• → Foo
• → → bar 1
• → → bar 2
• Links
• → yaslaw´s tolle Seite
• → tutorials
===HTML-Ansicht=== • Home
• → Foo
• → → bar 1
• → → bar 2
• Links
• → yaslaw´s tolle Seite
• → tutorials
=====Die Datei AdjacencyTree.php===== Die Datei AdjacencyTree.php beinhaltet die Klassen AdjacencyTree, AdjacencyTreeNode und IAdjacencyTreeNode. Diese muss eigentlich nicht angepasst werden um Alle Anpassungen sieht man im Kapitel Beispiele. * - IAdjacencyTreeNode Interface der Nodeclass * - AdjacencyTreeNode Defaultnodeclass */ /** * Tree Class * ArrayObjct * @see ArrayObject */ class AdjacencyTree extends ArrayObject{ const NODE_INTERFACE = 'IAdjacencyTreeNode'; const DEFAULT_NODE_CLASS = 'AdjacencyTreeNode'; protected $nodeClass; protected $rootId; /** * Konstruktor * @param String $nodeClass Der Name der Node-Klasse mit dem Interface IAdjacencyTreeNode * @param String $rootKey ID des RootElementes */ public function __construct($nodeClass = self::DEFAULT_NODE_CLASS, $rootId = 0){ parent::__construct(array()); $this->nodeClass = $nodeClass; $this->rootId = $rootId; } /** * Hinzufügen eines Nodes. Es wird geprüft dass es ein Objekt der Klasse AdjacencyTreeNode ist. * @see ArrayObject::append() */ public function append($value){ if(is_a($value, self::NODE_INTERFACE)){ $this->offsetSet($value->id, $value); }else{ throw new ErrorException('Node-Class is not from Type ' . self::NODE_INTERFACE); } } /** * Wechselt den ganzen Array aus. Jedes ArrayItem wird auf das Interface IAdjacencyTreeNode geprüft * @param Array $input Neuer Node-Array * @see ArrayObject::exchangeArray() */ public function exchangeArray($input){ parent::exchangeArray(array()); foreach($input as $node){ $this->append($node); } } /** * Direktes Erstellen eines Nodes * @param Integer $id ID des Nodes * @param Integer $parentId ID des Parentnodes * @param String $title Beschriftung * @param Array $paramArray Ein assozierter Array mit weiteren Informationen für den Node */ public function createNode($id, $parentId, $title, $paramArray = null){ // $node = $this->nodeClass::create($id, $parentId, $title); $node = call_user_func("{$this->nodeClass}::create", $id, $parentId, $title); if(is_array($paramArray)){ foreach($paramArray as $name => $value){ $node->$name = $value; } } $this->append($node); return $node; } /** * Starte die Berechnung des Trees */ public function calcTree(){ //Den Tree aufbauen $this->addTreeInfos($this->rootId); //und sortieren $this->uasort(array($this, 'sortByNodeSort')); } /** * Wird intern für die Sortirung gebraucht */ public static function sortByNodeSort($a, $b){ if ($a->getSort() == $b->getSort()) { return 0; } return ($a->getSort() < $b->getSort()) ? -1 : 1; } /** * Nachträgliches Setzen des Root-ID * @param String rootId */ public function setRootKey($rootId){ $this->rootId = $rootId; } /** * Dei rekursive Funktion für die Treeberechnung * @param Integer $parentId ID des Parentnodes * @param String $sortParent Sortierung des ParentNodes * @param Integer $level Level dieses Nodes */ private function addTreeInfos($parent, $sortParent = '', $lvl = 0){ $iter = $this->getIterator(); foreach($iter as $key => &$node){ if((string) $node->getParentId() == (string) $parent){ $node->addTreeInfos($sortParent, $lvl); //Und dasselbe für alle Kinder des aktellen Nodes $this->addTreeInfos($node->getId(), (string)$node->getSort(), $lvl+1); } } } } /** * Interface der Node-Class * Wird verwendet wenn man eine eigene Klasse schreiben will */ interface IAdjacencyTreeNode{ /** * Konstruktor * @param Integer $id ID des Nodes * @param Integer $parentId ID des Parentnodes * @param String $title Beschriftung */ public static function create($id, $parentId, $title); /** * TreeInfos des Nodes erstellen * @param String $sortParent Sortierung des ParentNodes * @param Integer $level Level dieses Nodes */ public function addTreeInfos($sortParent, $level); /** * Getter aller Properties die in der TreeClass verwendet werden */ public function getId(); public function getParentId(); public function getSort(); } /** * Default Node Class * Ein einzelner Node des Trees * SOlange das Interface verwendet wird, kann auch eine eigene Node-Class geschrieben werden */ class AdjacencyTreeNode implements IAdjacencyTreeNode{ private $id; private $parentId; private $title; private $sort; private $level; private $treeText; private $order; /** * Damit Ableitungen mit verschiedenen Konstrukteuren arbeiten können, ist dieser nicht genau deklariert * Es kann aber ein Argument als Array oder Objekt übergeben werden. Alle Elemente des Array (Properties des Objects) * werden als Propierties im Node erstellt. Key (PropetyName) => value * Es ist dabei nicht wichtig ob es sich um die 3 Key-Argumente handelt oder nicht. * @example * $node = new AdjacencyTreeNode(); * @example * $node = new AdjacencyTreeNode(array('id'=>1, 'parentId'=>5, 'tooltip'=>'hallo Welt')); * @example * $node = $pdoStmt->fetch(PDO::FETCH_CLASS, 'AdjacencyTreeNode') */ public function __construct(){ //Prüfen ob Argumente mitgegeben wurden und das erste Argument auslesen if(func_num_args() > 1 && false !== ($arg0 = func_get_arg(0))){ //Falls es sich um ein Obejkt handelt, alle Properties in einen Array lesen if(is_object($arg0)) $arg0 = get_object_vars($arg0); if(is_array($arg0)){ //Alle Elemente des Arrays übernehmen foreach($arg0 as $name => $value){ $this->$name = $value; } } } } /** * Konstruktor * @param Integer $id ID des Nodes * @param Integer $parentId ID des Parentnodes * @param String $title Beschriftung */ public static function create($id, $parentId, $title){ $instance = new AdjacencyTreeNode(); $instance->id = $id; $instance->parentId = $parentId; $instance->title = $title; return $instance; } /** * TreeInfos des Nodes erstellen * @param String $sortParent Sortierung des ParentNodes * @param Integer $level Level dieses Nodes */ public function addTreeInfos($sortParent, $level){ $this->level = $level; $this->setSortText($sortParent); $this->createTreeText(); } /** * Setzt den TreeText des Nodes */ protected function createTreeText(){ $this->treeText = str_repeat('-- ', $this->level) . $this->title; } /** * Setzt die Sortierung richtig. In diesem Fall nach id * @param String $sortParent Sortierung des ParentNodes */ protected function setSortText($sortParent){ $this->sort = sprintf('%s.[%s]', $sortParent, $this->id); } /** * generic Getter & Setter */ public function __set($name, $value){$this->$name = $value;} public function __get($name){ //Falls ein Parameter fehlt, diesen mit NULL zurückgeben und jeden Fehler ignorieren $errLvl = error_reporting(); error_reporting(0); $retVal = $this->$name; error_reporting($errLvl); return $retVal; } /** * Getter aller Properties die in der TreeClass verwendet werden */ public function getId(){ return $this->id;} public function getParentId(){ return $this->parentId;} public function getSort(){ return $this->sort;} } ?> {{tag>PHP Library Tree}}