Tutorium - smiffy.de

Werbung
Datenbanken & Informationssysteme II
Tutorium zum Aufbau einer objektrelationalen
Abbildungsschicht in PHP
(Version 2.8 vom 18.11.2016)
• Einleitung
Im folgenden wird die Erstellung einer Klasse zur Realisierung einer OR-Schicht erläutert. Die
Klasse enthält die Basisfunktionalität zum Anlegen, Auslesen, Ändern und Löschen von Datensätzen (konkret Schiffe). Das vorliegende Tutorium beschreibt die Implementierung einer möglichen
Zugriffsschicht so wie sie dann auch im nächsten Übungsblatt als Programmierrahmen vorgegeben ist.
• Datenbank
Zu Beginn legen wir erst mal die Datenbank segeltoern mit der Tabelle Schiff an. Dazu rufen wir in einem DOS-Fenster das Kommandozeilentool mysql.exe auf1:
mysql.exe -u root
Anschließend geben wir folgende Statements an:
drop database if exists segeltoern;
create database segeltoern;
use segeltoern;
CREATE TABLE schiff (
s_id bigint(20) NOT NULL,
s_name varchar(20) NOT NULL,
s_laenge double NOT NULL,
s_plaetze int(11) NOT NULL,
PRIMARY KEY (s_id)
);
Danach tragen wir mal zwei Schiffe ein:
insert into schiff (s_id, s_name, s_laenge, s_plaetze)
values(1, ’Fury’, 12.6, 7);
1. alternativ kannst du auch eine Oracle Datenbank verwenden.
Andreas Schmidt
PHP-Fingerübungen 1/11
Datenbanken & Informationssysteme II
insert into schiff (s_id, s_name, s_laenge, s_plaetze)
values(2, ’Olga II’, 15.1, 8);
Auflisten der beiden Datensätze mit:
select *
from schiff;
• CRUD-Schicht
Anschließend legen wir eine Datei mit dem Namen CRUD_Schiff.php an und
erstellen die folgende Klasse CRUD_Schiff mit Konstruktor, Getter-/Setter-Methoden sowie einer Methode __toString() zun Anzeigen eines Datensatzes:
<?php
class CRUD_Schiff {
protected $id;
protected $name;
protected $laenge;
protected $plaetze;
/**
* Konstruktor
*/
function __construct($id, $name, $laenge, $plaetze) {
$this->id = $id;
$this->name = $name;
$this->laenge = $laenge;
$this->plaetze = $plaetze;
}
/*
* GETTER
*/
public function getId() {
return $this->id;
}
Andreas Schmidt
PHP-Fingerübungen 2/11
Datenbanken & Informationssysteme II
public function getName() {
return $this->name;
}
public function getLaenge() {
return $this->laenge;
}
public function getPlaetze() {
return $this->plaetze;
}
/*
* SETTER
*/
public function setName($name) {
$this->name = $name;
}
public function setLaenge($laenge) {
$this->laenge = $laenge;
}
public function setPlaetze($plaetze) {
$this->plaetze = $plaetze;
}
/**
* toString Funktion
*/
public function __toString() {
$str = "$this->id: $this->name - $this->laenge ";
$str .= "$this->plaetze Plaetze";
return $str;
}
} ## Ende Klasse CRUD_Schiff
Andreas Schmidt
PHP-Fingerübungen 3/11
Datenbanken & Informationssysteme II
Schreib jetzt ein kleines Testprogramm, das eine Schiffsinstanz erzeugt und diese
mittels print ausgibt.
Als nächstes erstellen wir in der Klasse CRUD_Schiff eine Klassenmethode
getAll(), welche alle Schiffe zurückliefern soll:
public static function getAll() {
// SQL-Statement
$sql = "select s_id as id, s_name as name, s_laenge as
laenge, s_plaetze as plaetze
from schiff";
$list = MDB2_Util::query($sql);
$result = array();
foreach ($list as $s)
$result[] = new CRUD_Schiff($s['id'], $s['name'],
$s['laenge'], $s['plaetze']);
return $result;
}
Die Methode nutzt die Hilfsklasse MDB2_Util, welche du dir von der Homepage herunterladen kannst. Konkret wird die Klassenmethode query(...) genutzt,
welche ein SQL-Statement entgegen nimmt und ein Array der Ergebnisdatensätze
zurückliefert. Jeder Datensatz wird als Dictionary zurückgeliefert, dessen Schlüssel die (Alias) Namen der Spalten des Select-Statements sind. Um dir die Struktur
des Resultats anzuschauen kannst du den Befehl print_r($list); hinter den
Methodenaufruf schreiben. Anschließend wird über die Ergebnisdatensätze iteriert und Instanzen der Klasse CRUD_Schiff erzeugt. Damit die Hilfsklasse
MDB2_Util genutzt werden kann muss diese zuvor noch eingebunden werden.
Dies geschieht zu Beginn der Datei (aber innerhalb des PHP-Blocks) durch den
Befehl include 'MDB2_Util.php';
Unsere bisherige Implementierung können wir jetzt durch das folgende Programm
testen1:
Andreas Schmidt
PHP-Fingerübungen 4/11
Datenbanken & Informationssysteme II
<?php
include 'CRUD_Schiff.php';
include 'MDB2_Util.php';
$dsn = 'mysql://root:@localhost/segeltoern';
MDB2_Util::connect($dsn);
foreach (CRUD_Schiff::getAll() as $schiff)
echo $schiff,"\n";
MDB2_Util::close();
Das Programm führen wir von der Kommandozeile einer DOS-/oder CYGWIN- Box
aus:
$ php.exe1 test.php
und die Ausgabe sollte in etwa wiefolgt aussehen:
1: Fury - 12.6 7 Plaetze
2: Olga II - 15.1 8 Plaetze
Im nächsten Schritt soll die CRUD-Schicht um eine Methode zum anlegen von Datensätzen erweitert werden Dazu erstellen wir eine statische Methode create(...), welche
sowohl den Datensatz in der Datenbank anlegt, als auch eine Instanz des Datensatzes
zurückliefert:
public static function create($name, $laenge, $plaetze) {
$id = MDB2_Util::create_id('schiff')+1000;
// SQL-Statement
$sql = "insert into schiff (s_id, s_name, s_laenge, s_plaetze)
values (?, ?, ?, ?)";
// execute query
$data = array($id, $name, $laenge, $plaetze);
$affected_rows = MDB2_Util::query($sql, $data);
if ($affected_rows!=1)
die("Fehler: Datensatz konnte nicht angelegt werden!");
return new CRUD_Schiff($id, $name, $laenge, $plaetze);
}
1. Wenn du eine Oracle Datenbank benutzt, musst du den Connection-String entsprechend anpassen. (etwa so:
oci8://<username>:<passwort>@oracledbwi)
1. Im Poolraum bitte die PHP-Version unter h:/schmidt/db2-php.bat verwenden.
Andreas Schmidt
PHP-Fingerübungen 5/11
Datenbanken & Informationssysteme II
Die Methode erzeugt zu Beginn mittels einer weiteren Hilfsmethode
(create_id(...)) der Klasse MDB2_Util eine eindeutige ID für das Schiff
und bastelt dann das Insert-Statement mit Platzhaltern (?) zusammen. Anschließend werden in einem Array ($data) die Werte für die Platzhalter eingetragen
und das ganze dann wieder über die Methode query(...) in die Datenbank
eingetragen. Im Unterschied zu einem Select-Statement, das die Ergebnisdatensätze in einem Array zurückliefert wird im Falle eines insert,update/oder delete
Statements die Anzahl der vom SQL-Statement betroffenen Datensätze zurückgeliefert, bei diesem insert-Statement logischerweise 1. Wird eine anderer Werzt zurückgeliefert so ist irgendwas falsch gelaufen und wir geben eine Fehlermeldung
aus. Zum Abschluss der Methode wird noch eine Instanz mit den Werten erzeugt
und diese zurückgeliefert.
Als nächstes werden wir eine Methode zum löschen eines Datensatzes implementieren. Dies wird durch eine Instanzenmethode realisiert.
public function delete() {
// SQL-Statement
$sql = "delete from schiff
where s_id = ?";
// execute statement
$result = MDB2_Util::query($sql, array($this->id));
// error handling
if ($result != 1)
die("Fehler: Schiff ($id) wurde nicht aus der Tabelle
gelöscht");
unset($this);
}
Die Methode ist relativ selbsterklärend, lediglich das unset($this) am Ende
sei noch etwas genauer erläutert: unset(...) zerstört die übergebene Variable
(hier: die Instanz selbst), so dass diese später vom Programm aus nicht mehr verwendet werden kann.
Als nächstes soll von dir die statische Methode getByID($id) implementiert
Andreas Schmidt
PHP-Fingerübungen 6/11
Datenbanken & Informationssysteme II
werden. Die Methode soll genau eine Instanz zurückliefern, die durch die ID ($id)
spezifiziert wird. Nimm als Vorlage die zuvor implementierte Methode getAll()
und ändere sie entsprechend ab. Wird kein Schiff mit der entsprechenden ID gefunden, so brich mit einer Fehlermeldung ab.
Mittels der setter-Methoden kann eine Instanz modifiziert werden, die Änderungen
werden jedoch nicht in die Datenbank übertragen. Dazu benötigen wir eine Methode update(), welche den Zustand der Instanz mit der Datenbank synchronisiert:
public function update() {
// SQL-Statement
$sql = "update schiff
set s_name = ?, s_laenge = ?, s_plaetze = ?
where s_id = ?";
// Parameter array
$data = array($this->name, $this->laenge,
$this->plaetze, $this->id);
// execute query
$result = MDB2_Util::query($sql, $data);
}
Soweit zur Implementierung einer einfachen OR-Schicht für eine Klasse. Eine weitere sinnvolle Funktionalität liefert beispielsweise die Klassenmethode getByCondition($cond). In der einfachsten Implementierung wird die Bedingung
direkt in Form einer SQL-Bedingung formuliert und innerhalb der Methode an das
Select-Statement angehängt. Beachte aber, dass dies der SQL-Injection Tür und Tor
öffnet, wenn man nicht vorsichtig ist und die Bedingung auf „gemeine Zeichen“1
hin überpürft.
• 1:n-Beziehung
Als nächstes soll die Datenbank zusätzlich um eine Tabelle Hafen erweitert werden.
Gleichzeitig soll jedes Schiff einen Heimathafen besitzen. Hierbei handelt es sich
um eine klassische 1:n-Beziehung, die wie wir wissen mittels Fremdschlüssel in der
Datenbank realisiert wird. Die Befehle zum Anlegen der Tabelle Hafen, dem hin1. Beispielsweise Anführungszeichen und Kommentare, wenn die Zeichenkette welche die bedingung forrmuliert auch aus Benutzereingaben aufgebaut wird.
Andreas Schmidt
PHP-Fingerübungen 7/11
Datenbanken & Informationssysteme II
zufügen des Fremdschlüssels s_hafen_fk und zweier Beispieldatensätze lauten
wiefolgt:
CREATE TABLE hafen (
h_id bigint(20) NOT NULL,
h_name varchar(50) NOT NULL,
h_ort varchar(50) NOT NULL,
h_anlegeplaetze int(11) NULL,
PRIMARY KEY (h_id)
);
ALTER TABLE schiff
add s_hafen_fk bigint(20) references hafen(h_id);
insert into hafen (h_id, h_name, h_ort)
values(1, 'English Harbour','Nelsons Dock (Antiqua)');
insert into hafen (h_id, h_name, h_ort)
values(2, 'Marine de Fort','Point a Pitre (Guadeloupe)');
Als nächstes muss nun die zuvor entwickelte OR-Schicht der Klasse
CRUD_Schiff um dieses Attribut erweitert werden. Dazu muss dass Attribut als
Instanzenvariable in der Klasse definiert werden, im Konstruktor Erwähnung finden und auch überall dort wo SQL-Statements vorkommen (außer natürlich bei
delete()). Führe diese Änderungen zur Übung durch. Überprüfe deine Modifikationen1 indem du einem der beiden Schiffe einen Heimathafen zuweist und das
Testprogramm durchlaufen lässt. Erweitere gegebenenfalls das Testprogramm.
Erstelle weiterhin die Klasse CRUD_Hafen mit den notwendigen Instanzenvariablen, einem Konstruktor, der Getter-Methode getId(), einer statischen Methode getById($id)2 und der Methode __toString().
1. Wird das zusätzliche Attribut in die Datenbank eingetragen und wieder ausgelesen ?
2. Funktionalität analog zu der der Klasse CRUD_Schiff.
Andreas Schmidt
PHP-Fingerübungen 8/11
Datenbanken & Informationssysteme II
Als nächstes erstellen wir eine Methode getHafen(...), welche eine Hafeninstanz
zurückliefern soll. Die Implementierung sieht wiefolgt aus:
public function getHafen() {
if ($this->hafen_fk)
return CRUD_Hafen::getById($this->hafen_fk);
else
return null;
}
Die Implementierng überprüft zuerst, ob bei der Instanz die Instanzenvariable
hafen_fk gesetzt wurde (andernfalls hat das Schiff keinen Heimathafen). Ist dies
der fall so wird die Klassenmethode getById(...) der Klasse CRUD_Hafen aufgerufen, welche die Hafeninstanz zurückliefert.
Analog dazu kann enine Methode setHafen($hafen_instanz) definiert
werden, welche einem Schiff einen Heimathafen zuordnetm (siehe
Übnungsblatt 2).
Soll nun zusätzliche Applikationslogik mit hinzu programmiert werden, so geschieht dies nicht in der CRUD-Klasser, sondern in einer von der CRUD-Klasse
abgeleiteten Klasse. Die minimale Implementierung sieht wiefolgt aus:
<?php
require_once('CRUD_Schiff.php');
class Schiff extends CRUD_Schiff {
?>
}
Existiert eine abgeleitete Applikationslogikklasse so muss überall in der CRUDSchicht wo Instanzen der CRUD-Schicht erzeugt werden, die Erzeugung von Instanzen der abgeleiteten Klasse erfolgen - sonst geht die schöne Appliationslogik
gleich wieder flöten ;-).
Andreas Schmidt
PHP-Fingerübungen 9/11
Datenbanken & Informationssysteme II
• Dictionary als Konstruktorparameter:
Setzt man beim Konstruktor statt mehrerer Parameter einen assoziativen Array
ein, so kann man die Realisierung der CRUD-Schicht etwas vereinfachen. Beispielswiese sieht der Konstruktor der Klasse CRUD-Schiff dann wiefolgt aus:
function __construct($dic) {
$this->id = $dic['id'];
$this->name = $dic['name'];
$this->laenge = $dic['laenge'];
$this->plaetze = $dic['plaetze'];
$this->hafen_fk = $dic['hafen_fk'];
}
Der Vorteil bei dieser Implementierung ist, dass bei Select-Anfragen die Ergebnisdatensätze in Form eines Dictionaries zurückgeliefert werden, und man deshalb folgendes schreiben kann:
$class ='Film';
$res = $stmt->execute($data);
$results = array();
while($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC))
$results[] = new $class($row);
return $results;
Wie man sieht ist innerhalb der Schleife, welche die Datensätze aus der datenbank
abholt und die Instanzen erzeugt kein klassenspezifischer Code mehr vorhanden.
Alle Konstruktoren besitzen einen Parameter (das Dioctionary) und das Erzeugen
der Instanzen der entsprechenden Klasse kannn durch den Parameter $class erfolgen. Dieser sachverhalt erlaubt uns die Definition einer weiteren Methode in
der Klasse MDB2_Util, die wie query(...) funktioniert, jedoch bereits Instanzen
der angegebenen Klasse erzeugt. Dies führt zu einer Reduktion des notwenigen
Codes und zu einer höheren Performance, da die liste der Ergebnisdatensätze kein
zweites mal (zum Erzeugen der Instanzen) durchlaufen werden muss. So reduziert
Andreas Schmidt
PHP-Fingerübungen 10/11
Datenbanken & Informationssysteme II
sich zum Beispiel der Code für die Methode CRUD_Schiff::getAll() auf:
public static function getAll() {
// SQL-Statement
$sql = "select s_id as id, s_name as name, s_laenge as
laenge, s_plaetze as plaetze
from schiff";
return MDB2_Util::object_query($sql, 'Schiff');
}
einfach, oder ?
Nach diesem Prinzip ist auch der Programmierrahmen für die 2. Übung aufgebaut.
Andreas Schmidt
PHP-Fingerübungen 11/11
Herunterladen