======[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}}