User Tools

Site Tools


php:libraries:adjacencytreeclass

[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] 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.

Anwendungsbeispiele

Beispiel 1) Einfache Grundanwendung

Ganz einfach den Tree mit allen Standarteinstellungen ausgeben

Code

<?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";
}
?>

Erstellte HTML-Source

Home<br />
-- Foo<br />
-- -- bar 1<br />
-- -- bar 2<br />
Links<br />
-- tutorials<br />
-- yaslaw´s tolle Seite<br />

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

<?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 = '&bull; ' . str_repeat('&rarr; ', $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);
}
?>

Erstellte HTML-Source

<a href='' title='zurück zum Anfang'>&bull; Home</a><br />
<a href='foo' title=''>&bull; &rarr; Foo</a><br />
<a href='bar' title=''>&bull; &rarr; &rarr; bar 1</a><br />
<a href='bar' title='noch ein bar'>&bull; &rarr; &rarr; bar 2</a><br />
<a href='' title='meine Links'>&bull; Links</a><br />
<a href='http://yaslaw.info' title='gehe zu los, ohne..'>&bull; &rarr; yaslaw´s tolle Seite</a><br />
<a href='http://www.tutorials.de' title=''>&bull; &rarr; tutorials</a><br />

HTML-Ansicht

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.

AdjacencyTree.php
<?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;}    
 
}
?>
php/libraries/adjacencytreeclass.txt · Last modified: 27.01.2014 00:07:56 (external edit)