Eine Klasse um einen Adjacency-Tree in PHP aufzulösen und sauber auszugeben. Das Ausgabeformat ist anpassbar.
Ich habe bereits im MySQL gezeigt, wie man einen solchen Tree auslesen kann: [MySQL] Adjacency Tree. 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: Formular-Select mit Hierarchie aufbauen (aus PHP-Array). Auf diese Frage hatte ich den Codeschnipsel [PHP] AdjacencyTree auslesen 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.
Ganz einfach den Tree mit allen Standarteinstellungen ausgeben
<?php //Die AdjacencyTree-Klassen laden include_once 'AdjacencyTree.php'; //Standart DB-Connection herstellen include_once '../../test/connect.php'; //TreeObject erstellen $tree = new AdjacencyTree(); $tree->setRootKey('root'); //Alternativ: //$tree = new AdjacencyTree(AdjacencyTree::DEFAULT_NODE_CLASS, 'root') //Die Daten aus der DB laden $sql = <<<SQL SELECT t.* FROM tbl_adjacency_tree AS t; SQL; $result = mysql_query($sql); //Die Nodes erstelle ich hier mal mit der createNode-Funktion von AdjacencyTree while($row = mysql_fetch_assoc($result)){ $tree->createNode($row['id'], $row['parent_id'], $row['title']); } //Tree berechnen $tree->calcTree(); //und ausgeben. foreach($tree as $node){ echo "{$node->treeText}<br />\n"; } ?>
Home<br /> -- Foo<br /> -- -- bar 1<br /> -- -- bar 2<br /> Links<br /> -- tutorials<br /> -- yaslaw´s tolle Seite<br />
Home
-- Foo
-- -- bar 1
-- -- bar 2
Links
-- tutorials
-- yaslaw´s tolle Seite
//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'])); }
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.
<?php //Die AdjacencyTree-Klassen laden include_once 'AdjacencyTree.php'; //$pdo als PDO-Objekt erstellen. Ich hab das hier mal ausgelagert include_once '../../test/connectPDO.php'; /** * Beispiel für eine indiviudale Anwendung des Trees * Eine Ableitung der Node-Class bei der die Sortierung und der Ausgabetext geändert ist */ class AdjacencyTreeNode1 extends AdjacencyTreeNode{ /** * TreeText der ausgegeben werden soll. In diesem Fall mit eienm Ball gefolgt von Pfeilen und dem TreeText * @see AdjacencyTreeNode::createTreeText() */ protected function createTreeText(){ $this->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 = <<<SQL -- Sicherstellen dass die Felder gleich heissen wie die Properties der Node-Klasse, -- denn so können wir sie über PDO::fetchAll() einfach erstellen SELECT t.parent_id AS parentId, t.* FROM tbl_adjacency_tree AS t; SQL; $stmt = $pdo->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("<a href='%s' title='%s'>%s</a><br />\n", $node->value, $node->tooltip, $node->treeText); } ?>
<a href='' title='zurück zum Anfang'>• Home</a><br /> <a href='foo' title=''>• → Foo</a><br /> <a href='bar' title=''>• → → bar 1</a><br /> <a href='bar' title='noch ein bar'>• → → bar 2</a><br /> <a href='' title='meine Links'>• Links</a><br /> <a href='http://yaslaw.info' title='gehe zu los, ohne..'>• → yaslaw´s tolle Seite</a><br /> <a href='http://www.tutorials.de' title=''>• → tutorials</a><br />
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.
<?php /** * mpl by ERB software * @author stefan.erb(at)erb-software.com * * Inhalt dieser Datei: * - AdjacencyTree Treeclass ArrayObjct<IAdjacencyTreeNode> * - IAdjacencyTreeNode Interface der Nodeclass * - AdjacencyTreeNode Defaultnodeclass */ /** * Tree Class * ArrayObjct<IAdjacencyTreeNode> * @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<IAdjacencyTreeNode> $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;} } ?>