Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 PEAR – Einführung
2 Authentication
3 Caching
4 Date and Time
5 File Formats
6 HTTP
7 Internationalization
8 Mail
9 Networking
10 PHP
11 Text
12 Web Services
13 Benchmarking
14 Configuration
15 Database
16 File System
17 HTML
18 Images
19 Logging
20 Math
21 Numbers
22 Tools and Utilities
23 XML
24 Selbst Pakete erstellen
25 PECL
Index
Ihre Meinung?

Spacer
 <<   zurück
PHP PEAR von Carsten Möhrke
Anwendung und Entwicklung – Erweiterungen für PHP schreiben
Buch: PHP PEAR

PHP PEAR
798 S., 39,90 Euro
Rheinwerk Computing
ISBN 3-89842-580-0
gp 15 Database
  gp 15.1 DB
    gp 15.1.1 prepare(  ) und execute(  )
    gp 15.1.2 Informationen über Tabellen und Datenbank
    gp 15.1.3 Quoting/Escaping
    gp 15.1.4 Transaktionsorientierung
  gp 15.2 DB_NestedSet
  gp 15.3 DB_QueryTool
  gp 15.4 DB_Table
    gp 15.4.1 Einfügen von Werten
    gp 15.4.2 Nutzung von Formularen
    gp 15.4.3 Auslesen von Werten
    gp 15.4.4 Manipulation von Daten


Rheinwerk Computing

15.2 DB_NestedSet  toptop


Besprochene Version: 1.3.6 Lizenz: PHP-Lizenz 2
Klassendatei(en): DB/NestedSet.php; DB/NestedSet/Output.php

Das Paket DB_NestedSet ist dafür gedacht, Baumstrukturen mithilfe von Nested-Sets in einer Datenbanktabelle abzulegen. In vielen Fällen ist es üblich, einen Baum als eine hierarchische Struktur in einer Datenbanktabelle abzulegen. Das heißt, dass jedes Elternelement die IDs der Kindelemente kennt. Das Auslesen solcher Bäume ist allerdings recht aufwändig, da dies üblicherweise rekursiv stattfindet.

Wird ein Baum als Nested-Set abgelegt, so werden die Teilbäume als Mengen betrachtet, die ineinander verschachtelt werden. Das bietet bei der Verarbeitung, genauer gesagt beim Auslesen, dieser Strukturen einen deutlichen Geschwindigkeitsvorteil.

Ein besonders interessantes Feature dieses Pakets ist, dass die Daten gleich als Menü ausgegeben werden können.

Da Sie sich nicht weiter mit der Organisation der Daten in der Datenbank-Tabelle auskennen müssen, um das Paket zu nutzen, werde ich nicht weiter auf die Datenstruktur eingehen. Sollten Sie Interesse am Aufbau dieser Datenstruktur haben, finden Sie weitere Informationen im Internet, zum Beispiel auf der Seite von Kristian Köhntopp, die Sie unter der URL http://kris.koehntopp.de aufrufen können.

Das Paket kann mithilfe von PEAR::DB, PEAR::MDB oder PEAR::MDB2 auf die Datenbank zugreifen.

Die Tabelle zur Verwaltung der Daten kann mit dem folgenden Befehl angelegt werden:

CREATE TABLE tb_nodes ( 
  STRID int(11) NOT NULL auto_increment, 
  ROOTID int(11) NOT NULL default '0', 
  l int(11) NOT NULL default '0', 
  r int(11) NOT NULL default '0', 
  PARENT int(11) NOT NULL default '0', 
  STREH int(11) NOT NULL default '0', 
  LEVEL int(11) NOT NULL default '0', 
  STRNA char(128) NOT NULL default '', 
  PRIMARY KEY  (STRID), 
  KEY ROOTID (ROOTID), 
  KEY STREH (STREH), 
  KEY l (l), 
  KEY r (r), 
  KEY LEVEL (LEVEL), 
  KEY SRLR (ROOTID,l,r), 
  KEY parent (PARENT) 
) TYPE=MyISAM COMMENT='NestedSet table';

Listing 15.3 SQL-Befehl zum Anlegen der Datentabelle

In der Spalte STRNA werden die eigentlichen Daten, auch Payload genannt, abgelegt. Vor diesem Hintergrund sind Sie bei dieser Spalte natürlich nicht auf den Datentyp oder die Feldgröße festgelegt.

Diese Tabelle stellt die Mindestanforderung dar. Zusätzlich können Sie noch weitere Spalten definieren. Das kann gerade dann, wenn Sie eine Navigation mithilfe dieses Pakets aufbauen wollen, sehr sinnvoll sein, da Sie dann eine zusätzliche Spalte für die URL vorsehen können.

Um Probleme bei der Manipulation der Daten zu verhindern, wird eine zweite Tabelle genutzt, die zum Sperren der datenführenden Tabelle dient. Diese Tabelle ist mit diesem Befehl anzulegen:

CREATE TABLE tb_locks ( 
  lockID char(32) NOT NULL default '', 
  lockTable char(32) NOT NULL default '', 
  lockStamp int(11) NOT NULL default '0', 
  PRIMARY KEY  (lockID,lockTable) 
) TYPE=MyISAM COMMENT='Table locks for NestedSet';

Listing 15.4 Befehl zum Anlegen der Locking-Tabelle

Nachdem Sie die Tabellen angelegt haben, stellt sich natürlich die Frage, wie Sie die entsprechenden Daten darin ablegen können. Zuerst benötigen Sie ein DB_NestedSet-Objekt, das Sie mit der Methode factory() ableiten können. Diese Methode bekommt als ersten Parameter den Namen des Datenbanktreibers, also 'DB', 'MDB' oder 'MDB2', übergeben. Danach folgt der DSN, der zum Zugriff auf die Datenbank benötigt wird. An dritter Stelle können Sie noch ein Array mit Parametern übergeben, wie Sie gleich sehen werden.

Sollten Sie Tabellen mit einem anderen als dem vorgegebenen Namen nutzen, ist das kein Problem. In dem Fall müssten Sie die Namen der Tabellen mit der Methode setAttr() beim System anmelden.

$nestedSet->setAttr(array( 
      'node_table' => 'tb_nodes', 
      'lock_table' => 'tb_locks' 
   ) 
);

Wie Sie sehen können, bekommt die Methode ein Array mit den beiden Schlüsseln 'node_table' und 'lock_table' übergeben, die die Namen der entsprechenden Tabellen enthalten.

Um einen neuen Baum aufzubauen, benötigen Sie ein Root-Element, unter dem dann die anderen Daten eingehängt werden können. Das geschieht mit der Methode createRootNode(). Wichtig ist, dass Sie in einer Tabelle mit mehreren Bäumen arbeiten können. Das heißt, Sie können mehrere Root-Elemente mithilfe von createRootNode() anlegen. Die Methode bekommt den Datensatz, der eingefügt werden soll, als Array übergeben. Dies gilt auch für die anderen Methoden, die zum Einfügen von Daten vorgesehen sind. Die Nutzung eines Arrays erscheint ein wenig umständlich, gibt »Powerusern« aber ein größeres Maß an Flexibilität. In den folgenden Beispielen wird nur der Schlüssel 'STRNA' genutzt, mit dem der zu speichernde Wert, also die so genannte Payload, übergeben wird. Verfügt Ihre Tabelle noch über eine zusätzliche Spalte, nutzen Sie den Spaltennamen als Schlüssel und übergeben damit den dazugehörigen Wert.

Die Methode gibt eine ID zurück, über die dieser Knoten angesprochen werden kann, was dann erforderlich ist, wenn neue Daten unterhalb dieses Knotens eingefügt werden sollen. Das geschieht mit der Methode createSubNode(). Als erster Parameter wird der Methode die ID des Knotens übergeben, unter dem die Daten eingefügt werden sollen. An zweiter Stelle folgt das Array, mit dem abzulegenden Datensatz. Auch diese Methode gibt die ID des neuen Datensatzes zurück, so dass Sie auch darunter Daten einfügen können.

Im Beispiel in Listing 15.5 wird ein Baum für eine kleine Menüstruktur aufgebaut.

require_once('DB/NestedSet.php'); 
 
$dsn = 'mysql://user:geheim@localhost/daten'; 
// Neues Objekt ableiten; Datenbankzugriff ueber DB 
$set = DB_NestedSet::factory('DB',$dsn); 
if (true == PEAR::isError($set)) 
{ 
   die ($set->getMessage()); 
} 
 
// Neues Root-Objekt ableiten 
$root = $set->createRootNode(array('STRNA' => 'Hauptmenü')); 
// Neuen Knoten fuer Hardware generieren 
$node_hard = $set->createSubNode($root, 
                               array('STRNA' => 'Hardware')); 
// Neuen Knoten fuer Software generieren 
$node_soft = $set->createSubNode($root, 
                               array('STRNA' => 'Software')); 
 
// Unterpunkte einfuegen 
$set->createSubNode($node_hard,array('STRNA' => 'Apple')); 
$set->createSubNode($node_hard,array('STRNA' => 'Intel')); 
 
$set->createSubNode($node_soft,array('STRNA' => 'Büro')); 
$set->createSubNode($node_soft,array('STRNA' => 'Spiele'));

Listing 15.5 Aufbau einer Menüstruktur mit DB_NestedSet

Die Methode createSubNode() fügt einen Knoten unterhalb eines Elements ein, wobei Sie keine Möglichkeit haben, die Reihenfolge der neuen Elemente zu beeinflussen. Möchten Sie die neuen Knoten in einer bestimmten Reihenfolge einfügen, stehen die Methoden createLeftNode() und createRightNode() zur Verfügung. Diese fügen einen neuen Knoten »neben« einem bestehenden Knoten ein. Das heißt, Sie legen erst mit createSubNode() eine neue Ebene unterhalb eines bestehenden Knotens an und können mit der ID dieses neuen Knotens neue Knoten mit createLeftNode() bzw. createRightNode() einfügen.

$node_apple = $set->createSubNode($node_hard, 
                                  array('STRNA' => 'Apple')); 
$node_apple_noteb = $set->createLeftNode($node_apple, 
                      array('STRNA' => 'Notebooks'), true);

Die Methoden benötigen als ersten Parameter die ID des Knotens, neben dem eingefügt werden soll, und danach folgt das Array mit den Daten. Wichtig ist, dass Sie an letzter Stelle den booleschen Wert true übergeben, da die Methode sonst keine ID, sondern ein Objekt zurückgibt. Bei diesem Verhalten handelt es sich ursprünglich um einen Fehler, der aber aus Kompatibilitätsgründen beibehalten werden muss.

Die primäre Zielsetzung des Pakets ist die Nutzung von Bäumen zur Ausgabe von Menüstrukturen. Daher kann das Paket auch direkt Menüs ausgeben. Hierzu wurden die folgenden Pakete als Grundlage genutzt:

  • CoolMenu Ein ausklappbares DHTML-/JavaScript-Menü, das Sie unter http:// www.dhtmlcentral.com/projects/coolmenus/ finden.
  • Tigra Menu Ein weiteres kostenloses auf DHTML basierendes Menü-Script, das Sie unter http://www.softcomplex.com/products/tigra_menu/ finden.
  • PEAR::HTML_Menu Auch das PEAR-Paket HTML_Menu können Sie für die Ausgabe der Daten nutzen. Informationen zu diesem Paket finden Sie in Abschnitt 17.11 dieses Buches.

Möchten Sie CoolMenu und TigraMenu nutzen, müssen Sie die entsprechenden JavaScript-Dateien herunterladen und entsprechend der Anleitung einbinden. Zur Nutzung von PEAR::HTML_Menu muss das Paket natürlich auf dem Server installiert sein.

Eine weitere Möglichkeit ist, die Daten direkt von dem Paket ausgeben zu lassen, wozu der Ausgabetreiber 'TreeMenu' vorgesehen ist. Zusätzlich ist es auch möglich, die enthaltenen Daten grafisch aufbereiten zu lassen, wozu das Paket PEAR::Image_GraphViz genutzt wird.

Die Nutzung der verschiedenen Menü-Arten gestaltet sich immer sehr ähnlich. Zuerst lesen Sie alle Daten aus der Tabelle aus, wozu die Methode getAllNodes() definiert ist. Der Methode wird als erster Parameter der Wert true übergeben, um sicherzustellen, dass die Daten in Form eines Arrays zurückgegeben werden. Übergeben Sie hier keinen Wert oder ein false, erhalten Sie ein Objekt, mit dem Sie aber nichts anfangen können. Bei dem zweiten Parameter handelt es sich auch um einen booleschen Wert. Hiermit wird festgelegt, ob die Schlüssel des Arrays den Namen der Spalten entsprechen sollen oder ob die Namen durch einen Alias ersetzt werden. Die Default-Einstellung ist, dass die Namen der Spalten ersetzt werden. Hierzu wird intern das folgende Array genutzt:

$params = array 
( 
"STRID"         =>      "id", 
"ROOTID"        =>      "rootid", 
"l"             =>      "l", 
"r"             =>      "r", 
"STREH"         =>      "norder", 
"LEVEL"         =>      "level", 
"STRNA"         =>      "name" 
);

Wie Sie sehen, werden die Namen der Datenbankspalten als Schlüssel genutzt und verweisen auf den Text, der als Alias genutzt werden soll. Diese Aliase können Sie, wie bereits angedeutet, mit einem Array definieren, das Sie der Factory-Methode als dritten Parameter übergeben können. Das Array ist identisch aufgebaut mit dem hier dargestellten. Haben Sie in Ihrer Tabelle noch weitere Spalten definiert, um Links oder anderes abzulegen, müssen diese hier mit genannt werden, da der SELECT-Befehl zur Abfrage der Datenbank auf Basis dieses Arrays erstellt wird.

Das so erhaltene Array können Sie dann noch weiter verarbeiten oder ergänzen, was sehr hilfreich ist, wenn Sie beispielsweise noch eine URL hinzufügen müssen oder Ähnliches.

Die aufbereiteten Daten werden in einem Array abgelegt, in dem auch noch andere Definitionen vorgenommen werden können. Die Daten müssen unter dem Schlüssel 'structure' abgelegt werden. Die weiteren Definitionen hängen von der gewählten Ausgabemethode ab.

Das so konstruierte Array wird dann an die Methode factory()übergeben, die zur Klasse DB_NestedSet_Output gehört.

$output = DB_NestedSet_Output::factory($params, 'Menu');

An erster Stelle wird das Array mit Daten und sonstigen Definitionen übergeben. An zweiter Stelle folgt der Name der Menüstruktur, die genutzt werden soll. Neben 'Menu' für die Nutzung von PEAR::HTML_Menu können Sie hier auch 'TigraMenu', 'CoolMenu', 'TreeMenu' oder 'GraphViz' angeben. Nachfolgend werde ich nur die gebräuchlichsten Typen erläutern. Da die anderen Typen aber weitestgehend identisch sind, sollte es kein Problem für Sie sein, sich in die Nutzung eines anderen Typs einzuarbeiten.

Das so generierte Ausgabe-Objekt wird in Abhängigkeit von der gewünschten Darstellung unterschiedlich genutzt. Nutzen Sie HTML_Menu für die Ausgabe, können Sie die Daten mit der Methode returnStructure() aus dem Objekt auslesen und dann an ein HTML_Menu-Objekt übergeben.

Die Ausgabe als HTML_Menu könnte beispielsweise so umgesetzt werden:

require_once('HTML/Menu.php'); 
require_once('DB/NestedSet.php'); 
require_once('DB/NestedSet/Output.php'); 
$dsn = 'mysql://netviser:geheim@localhost/netviser'; 
$nestedSet = DB_NestedSet::factory('DB', $dsn); 
// Alle Knoten auslesen 
$data = $nestedSet->getAllNodes(true); 
// URL in das Array einfuegen 
foreach ($data as $id => $node) 
{ 
     $data[$id]['url'] = $_SERVER['PHP_ 
     SELF'].'?id 
     =' . $node['id']; 
} 
// Daten in das neue Array einfuegen 
$params = array ('structure' => $data); 
 
$output = DB_NestedSet_Output::factory($params,'Menu'); 
$structure = $output->returnStructure(); 
 
$menu =  new HTML_Menu($structure, 'sitemap'); 
$menu->show();

Listing 15.6 Ausgabe eines Menüs mit PEAR::HTML_Menu

Das so generierte Menü sehen Sie in Abbildung 15.1. Möchten Sie das Menü anders gestalten, können Sie natürlich auf alle Funktionalitäten zurückgreifen, die HTML_Menu bereitstellt. Informationen dazu finden Sie in Abschnitt 17.11.

Abbildung 15.1 Darstellung des Menüs im Browser

Die anderen Darstellungsvarianten nutzen alle die Methode printTree(), die das Menü direkt ausgibt. In dem folgenden – stark gekürzten – Beispiel wird der Ausgabetyp CoolMenu genutzt. Das Menü wird über ein umfangreiches Array ($options) konfiguriert. Da die Besprechung dieses Arrays hier leider den Rahmen sprengen würde, möchte ich Sie auf die Beispieldateien des Pakets verweisen. Dort finden Sie gut erläuterte Beispiele.

<?php 
require_once('DB/NestedSet.php'); 
require_once('DB/NestedSet/Output.php'); 
$dsn = 'mysql://netviser:geheim@localhost/netviser'; 
$nested = DB_NestedSet::factory('DB', $dsn); 
$daten = $nested->getAllNodes(true); 
// Einfuegen der URLs 
foreach ($daten as $id => $node) 
{ 
     $daten[$id]['url'] = $_SERVER['PHP_SELF'].'?id=' . $node['id']; 
} 
 
$params = array( 
            'structure' => $daten, 
            'textField' => 'name', // Arrayfeld fuer Beschriftung 
            'linkField' => 'url', // Arrayfeld mit der URL 
            'currentLevel' => 1  // Ausgabe-Ebene 
         ); 
 
$options = array( 
 
// Umfangreiches Array mit Optionen 
 
) 
 
// Ausgabe-Objekt ableiten 
$menu = DB_NestedSet_Output::factory($params, 'CoolMenu'); 
// Optionen fuer die Ausgabe festlegen 
$menu->setOptions('printTree', $options); 
?> 
<html> 
<body> 
<!-- Einbinden der JavaScript-Datei --> 
<script language="JavaScript1.2" src="coolmenus4.js"> 
</script> 
 
<?php 
// Ausgabe des eigentlichen Menues 
$menu->printTree(); 
?> 
 
</body> 
</html>

Listing 15.7 Ausgabe des Menüs mit CoolMenu

Das Ergebnis sehen Sie in Abbildung 15.2.

Abbildung 15.2 Darstellung im Browser

Mithilfe des Arrays $params wird festgelegt, welche Daten aus dem Array für die Darstellung des Menüs genutzt werden sollen. 'textField' und 'linkField' müssen jeweils den Namen des Array-Feldes enthalten, das den Text bzw. die URL des Links enthält.

Interessant ist das Element 'currentLevel', mit dem Sie festlegen, ab welcher Hierarchiestufe das Menü eingeblendet werden soll. Die 1 bedeutet, dass alle Elemente im Menü genutzt werden. Nutzen Sie hier eine 2, würde die erste Ebene der Daten übersprungen und es würden 2 Menüs (Hardware und Software) eingeblendet.

Die Nutzung des Typs TigraMenu ist seitens DB_NestedSet identisch. Die Änderungen, die sich ergeben, resultieren aus TigraMenu selbst. Auch in diesem Fall finden Sie ein umfangreiches Beispiel im Paket, das die Konfiguration von TigraMenu erläutert.

Neben den bereits vorgestellten, fertig konfektionierten Ausgabemöglichkeiten können Sie die Daten, die von getAllNodes() bereitgestellt werden, natürlich auch selbst aufbereiten.

Darüber hinaus sind auch Methoden definiert, mit denen Sie durch den Baum navigieren, einzelne Äste auslesen können und Ähnliches. Die Navigation innerhalb des Baumes basiert auf der ID der Elemente, die im Array übergeben wird.

Mit getRootNodes() können Sie alle Root-Elemente auslesen. Die Methode erwartet – genau wie getAllNodes() – ein true, damit Sie die Elemente als Array erhalten. Alle Kind-Elemente eines Knotens erhalten Sie mit der Methode getChildren(). Diese erwartet als ersten Parameter die ID des Knotens, von dem Sie die Kinder der nächsten Ebene ermitteln wollen, und als zweiten das schon bekannte true. Die »Verwandten« eines Knotens, also die Elemente, die sich »neben« ihm in derselben Ebene befinden, stellt die Methode getSiblings() zur Verfügung, die parameterkompatibel mit getChildren() ist.

Mit den Methoden getBranch() bzw. getSubBranch() können Sie einen Baum bzw. einen Teilbaum auslesen. getBranch() liefert den gesamten Baum zurück, [Der Name der Methode lässt vermuten, dass nur ein Ast ausgegeben werden soll. Nichtsdestotrotz liefert sie momentan den kompletten Baum. ] in dem sich ein Element befindet, dessen ID Sie an erster Stelle übergeben, wohingegen getSubBranch() nur alle Elemente zurückgibt, die unterhalb dieses Elements liegen, und das Element selbst ausschließt.

Um ein Element zu löschen, können Sie deleteNode() nutzen. Die Methode bekommt die ID des Knotens übergeben und entfernt ihn und alle dazugehörigen Kinder. Möchten Sie einen Knoten nur ändern, also die Payload verändern, können Sie updateNode() nutzen. Diese Methode bekommt die ID des Elements übergeben, das geändert werden soll, und darüber hinaus ein Array mit Daten, wie es auch bei createSubNode() Verwendung findet.

 <<   zurück
     
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: PHP PEAR
PHP PEAR
Jetzt Buch bestellen!
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: PHP 5.6 und MySQL 5.7






 PHP 5.6 und
 MySQL 5.7


Zum Rheinwerk-Shop: Einstieg in PHP 5.6 und MySQL 5.6






 Einstieg in PHP 5.6
 und MySQL 5.6


Zum Rheinwerk-Shop: Responsive Webdesign






 Responsive Webdesign


Zum Rheinwerk-Shop: Moderne Websites entwickeln






 Moderne Websites
 entwickeln


Zum Rheinwerk-Shop: MySQL 5.6






 MySQL 5.6


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo








Copyright © Rheinwerk Verlag GmbH 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern