1 Einführung
1.1 Grundsätzliches zum Workshop
Ich habe es mir so gedacht, dass ich anhand eines Beispielprojektes Stück für Stück eine OOP-Benutzerschnittstelle erstellen werde. Der Schwerpunkt dieser Aufgabe ist die Vermittlung der Denkweise, die man für OOP für seine Projekte braucht. Ich werde versuchen so simpel und eindeutig wie möglich zu sein, werde auf komplexeren Sachen möglichst verzichten, um keine Verwirrung zu erregen. Auch werde ich nicht so tief gehen, und die einzelnen Methoden, Funktionen entwickeln; die Konzentration soll allein der OOP gewidmet werden.
Für den weiteren Verlauf habe ich mir was mehr oder weniger Besonderes ausgedacht: Jeder kann mitwirken, jeder kann die Schnittstelle erweitern, jeder kann die Schnittstelle auf seine Weise benutzen. Das soll etwas Aufmunterung und vor allem Praxis in die Bude bringen. Näheres dazu erfährst Du am Ende.
Eine wichtige Frage wäre noch, für wen dieser Workshop nun geeignet wäre? In erster Linie sind es die Einsteiger, die bis jetzt ohne Klassen und Co. ausgekommen sind, und deren Interesse zur OOP (zur Praxis) nun gestiegen ist.
Hinweis: Die Benutzerschnittstelle wird für PHP 5 entwickelt.
1.2 Was benötigt wird
Wichtige Voraussetzung ist auf jeden Fall der Wunsch, OOP verstehen zu wollen. Wie bereits erwähnt, brauchen wir PHP 5 auf dem Webserver; eine Datenbank wird nicht benötigt, denn für unser Projekt werden wir der Übersicht halber einfach ein kleines Script mit Musterdaten erstellen. Die Dokumentation auf php.net - versteht sich von selbst. Für uns ist besonders
dieses Kapitel enorm von Bedeutung.
2 Der Start
1.1 Vorbereitungen
Richtige Planung ist die halbe Miete. Man sieht immer wieder, wie wenig Aufwand manche Entwickler in die Planung eines Projekts stecken, gerade bei PHP. Das liegt wohl daran, dass bei PHP auch keine Notwendigkeit besteht. Bei Programmiersprachen mit einer Bibliothek, die auf Klassen aufbaut, ist es schon anders, da muss man eben durch. Aber wenn es nicht notwendig ist, wozu dann die Mühe machen und Klassen entwerfen?
Auf den Punkt gebracht wurden die Klassen und die ganze OOP für den Menschen entwickelt. Die OOP soll die Programmierung erleichtern. Der Entwickler soll den Code schneller, logischer, sicherer entwerfen können. Man soll eindeutige Strukturen, Aufteilungen haben, um so wenig Verwirrung wie möglich zu schaffen.
Nun gut, wir wollen uns unser Projekt ansehen: Ein kleines CMS soll entstehen. Der User soll sich einloggen können, Bilder hochladen. Ein Rang-System regelt die Möglichkeiten der einzelnen User. Also eine Galerie mit einigen Extras.
Jetzt ist zu überlegen, wie man das Projekt aufteilen könnte. Man erstellt z.B. eine Liste mit Oberbegriffen:
- Userverwaltung
- Rangsystem
- Bildermanipulation/-verwaltung
Das, was wir hier haben lässt sich noch weiter aufteilen, in einzelne Klassen. Doch zuvor erstellen wir Ordner:
- Ordner „Include“
- Ordner „Include/Klassen“
- Ordner „Include/Funktionen“ (Für globale Funktionen)
1.2 Die kleine Datenbank zum Testen
Es wird eine PHP-Datei mit folgendem Inhalt erstellt:
PHP-Code:
<?php
/* db.inc.php */
$db_name = array('1' => 'Max', '2' => 'Hans', '3' => 'Werner', '4' => 'Siegfried', '5' => 'Tom');
$db_pass = array('1' => '123', '2' => '345', '3' => '567', '4' => '789', '5' => '987');
$db_rang = array('1' => 1, '2' => 0, '3' => 1, '4' => 3, '5' => 2);
?>
Man sieht schon, dass es sehr einfach ist, soll es auch. Der Index stellt die ID dar, über welche jeder Datensatz angesprochen werden kann.
Diese Datenbank wird immer
vor den ganzen Klassen-Dateien eingebunden, weil die Klassen damit direkt arbeiten. Es wird außerdem nicht möglich sein, die Datensätze dauerhaft zu manupulieren oder zu löschen. In folgenden Beispielen werden wir diese Datensätze zum Testen lediglich geändert ausgeben.
Die Datei speicherst Du bitte im Ordner „Include“ ab.
1.3 Motivation
Hinweis: Ab jetzt werden Ausdrücke aus OOP verwendet, deren Bedeutungen als Voraussetzung benötigt werden. Solltest Du Schwierigkeiten haben, so erweitere dein Wissen z.B. mit Hilfe von Links im Kapitel „Hilfreiche Links zum Thema OOP“.
Gleich nehmen wir uns erstmal die Userverwaltung vor, doch zuvor ewas Grundlegendes:
Es ist immer zu überlegen, wie die Oberkategorie in Klassen aufgeteilt werden soll. Bei unserem kleinen Projekt ist es nicht so aufwendig, dass wir komplexe Diagramme anfertigen sollten, aber hier einige Tips für die Zukunft: Als Hilfeleistung kann man die Substantive der jeweiligen Kategorie aufzählen. Bei der Kategorie Hausbau wären es z.B.: Baumaterial, Kosten, gesetzliche Regelung, Stromversorgung usw.
Eine logische Verbindung mit Hilfe von Klassen herzustellen ist jetzt die Kunst, die sich mit der Zeit entwickelt; wobei das jetzt nicht auf die Programmierung ankommt, sondern auf einen viel wichtigeren Teil - den Entwurf der Software.
Ich möchte aus denen, die mit OOP arbeiten wollen, die falschen Gedanken herausprügeln! OOP ist nicht mit Schleifen, Bits oder Bytes zu vergleichen, hier kann keine Lösung falsch oder richtig sein. Es ist eine andere Denkweise nötig, um an die OOP heranzugehen als an die Möglichkeiten einer Programmiersprache. OOP ist zum Beschreiben des Problems da, das Arbeiten mit Objekten kommt erst später. Wer jetzt noch
beim Entwurf eines OOP-Projekts mit Fragen wie „Wie kann ich in Klassen mit einer while-Schleife die Datenbankeinträge auslesen?“ ankommt, bekommt was auf die Nuss.
Immer gut ist eine Zeichnung, wo sich dann die einzelnen Unterkategorien (die besagten Substantive) befinden. Später knüpft man Verbindungen zwischen einzelnen Unterkategorien (meistens sind es gleich Klassen) und überlegt eine passende Beziehungsform (Vererbung, Abstraktion usw.). Was im Falle des Hausbaus als logisch erscheint ist u.a. die Beziehung zwischen Kosten und Baumaterial; es ist klar, das Material wird die Kosten steigen lassen, also könnte ein Objekt der Klasse „Baumaterial“ ein Objekt der Klasse „Kosten“ besitzen, und beim Bestellen neuer Ware automatisch das Guthaben reduzieren.
Du merkst vielleicht schon, dass die OOP die Ideen des Menschen besser umsetzen kann als Code-Zeilen, die leicht zu einem Salat werden, da jetzt sprachliche Begriffe (für die Klassen), und gut nachvollziehbare Verbindungen verwendet werden. Es wird nicht so sein, dass deine ersten Projekte gleich eine gute OOP-Struktur haben werden; wie gut sie aber werden können hängt von dir ab. Es kommt oft vor, dass der Nachbar deine Idee zur Umsetzung anguckt und einen anderen/besseren Weg vorschlägt - da solltest Du auf jeden Fall Neugier zeigen. Spaß sollte es machen, Überlegungen während der Zugfahrt zu verschiedenen Problemen des Lebens (Verwalten einer CD-Sammlung, Verkauf von Bierkisten und -flaschen...) und deren OOP-Lösungen ist wärmstens zu empfehlen.
Für den Entwurf der Software mit OOP gibt es sogar eigene Beschreibungssprachen wie
UML.
3 Unser Projekt
3.1 Gesagt, getan. Unser Projekt in der Praxis
Um auf unser Projekt mit der Galerie zurückzukommen, habe ich Folgendes in Aussicht:
Zu jeder Oberkategorie wird es schlicht und einfach eine Klasse geben. Da „Rangsystem“ mit „User“ zusammenhängt, wird die Klasse „Rang“ ein Element der Klasse „User“, „User::rang“ brauchen, um passende Optionen zu erlauben. Die Klasse für die Bildverarbeitung wird am unabhängigsten gehalten, sie wird einige Methoden für das Arbeiten mit Bildern anbieten.
Wir werden jetzt eine Klasse „User“ entwerfen, die so aussieht:
PHP-Code:
<?php
/* user.class.php */
class User
{
private $id; // Für die Datenbank
private $name;
private $passwort;
private $rang;
function __construct($id)
{
global $db_name;
global $db_pass;
global $db_rang;
// Werte aus der Datenbank holen und der Klasse zuweisen:
$this->id = $id;
$this->name = $db_name[$id];
$this->passwort = $db_pass[$id];
$this->rang = $db_rang[$id];
}
final public function get_id() { return $this->id; }
final public function get_name() { return $this->name; }
final public function get_passwort() { return $this->passwort; }
final public function get_rang() { return $this->rang; }
final public function change_pass($pass_alt, $pass_neu)
{
// Wir ändern unser Passwort:
if ($pass_alt == $this->passwort)
{
$db_pass[$id] = $pass_neu;
$this->passwort = $pass_neu;
return true;
}
else
{
return false;
}
}
}
?>
So, wir haben nun einen Konstruktor, der die ID als Argument erwartet, um den richtigen Datensatz in der Datenbank zu erwischen. Danach werden die Klassenelemente mit den passenden Daten aus der Datenbank initialisiert.
Die folgenden vier Methoden (get_id()...) erscheinen vielleicht absurd, aber es hat einen Grund: Da wir eine Schnittstelle entwickeln, soll diese auch möglichst sicher sein, denn die soll ja auch oft gebraucht werden. Das heißt hätte ich die Klassenelemente auf public gesetzt und keine Methoden erstellt, könnte ein Programmierer die Elemente aus Versehen oder ohne über die Konsequenzen nachgedacht zu haben ändern, und schon hat man ein Loch im System. Man stelle sich nur vor, dass sich die ID plötzlich ändert - das könnte unerwartete Folgen beim weiteren Arbeiten mit dem Objekt mit sich ziehen. Eine ähnliche Möglichkeit bietet die __get()-Methode, auf die habe ich allerdings in diesem Fall verzichtet, da sie einfach zu allgemein ist.
Zu dem final bleibt noch zu sagen, dass dies ebenfalls eine Sicherheitsmaßnahme ist, und zwar sollen diese final-Methoden nicht mehr von einer Kindklasse überladen werden können.
Die Klasse „User“ kommt nun als „user.class.php“ in den Ordner „Include/Klassen/“.
Als Nächstes wäre das Rangsystem am Zug. Man bedenkt, dass das Rangsystem in Verbindung mit Usern steht. Jeder User hat einen bestimmten Wert für das Rangsystem. Das Rangsystem muss also mit diesem Wert was anfangen können, eine Beziehung zwischen den Klassen wird entstehen:
PHP-Code:
<?php
/* rang.class.php */
class Rang
{
private $rang;
function __construct($rang)
{
$this->rang = $rang;
}
final public function avatar_upload()
{
// Ist man mit dem Rang berechtigt, ein Benutzerbild anzulegen?
switch ($this->rang)
{
case 4:
return true;
break;
case 3:
return true;
break;
default:
return false;
}
}
final public function big_pictures_upload()
{
// Nur bei $this->rang == 4 kann man große Bilder hochladen
return $this->rang == 4 ? true : false;
}
}
?>
Die Beziehung zwischen den beiden Klassen besteht darin, dass die Klasse „Rang“ einen Wert von „User“ erwartet, nämlich „User::rang“.
Man hat in OOP viele Arten von Beziehungen: z.B. eine Vererbung, die in diesem Fall erstens die Flexibilität vernachlässigen würde (man müsste immer ein Objekt vom „User“ haben, um im „Rang“ zu arbeiten), und zweitens wäre es leichtsinnig, unnötige Methoden und Elemente von „User“ mit sich zu schleppen. Ins Detail zu gehen, wann und wo man welche Beziehung verwenden sollte/könnte würde jetzt den Ramen sprengen; die Handhabung in Sachen Vererbung kommt mit der Zeit, mit der Praxis, weshalb wir auch klein anfangen.
Wir haben also den Konstruktor mit einem Argument - dem Rang-Wert „User::rang“. Anhand dieses Wertes funktionieren auch die beiden Methoden „Rang::avatar_upload()“ und „Rang::big_pictures_upload()“. Beide Methoden sind wieder als final gekennzeichnet. Bevor wir nicht wissen, wie sich eine mögliche Kindklasse in der Zukunft verhält, kann final auch nicht verkehrt sein.
„Rang::avatar_upload()“ prüft, ob mit dem vorgegebenen Rang ein Upload eines Avatars (Benutzerbildes) möglich wäre.
„Rang::big_pictures_upload()“ prüft, ob mit dem Rang große Bilder hochgeladen werden können.
Die Klasse „User“ bitte als „rang.class.php“ in den Ordner „Include/Klassen/“ legen.
Nun haben wir zwei von vier Bereichen abgedeckt, fehlt nur noch die Bildverwaltung als Klasse „Bild“:
PHP-Code:
<?php
/* bild.class.php */
class Bild
{
private $bildgroesse;
private $bildhoehe;
private $bildbreite;
public const $breite_thumbnail = 150; // Die Breite als Thumbnail
function __construct($bild)
{
// Ermitteln der Größe, Höhe, Breite.
// Um auf das Wesentliche einzugehen, definiere ich einfach feste Werte:
$this->bildgroesse = 512; // 512 kb
$this->bildghoehe = 800; // 800 px
$this->bildbreite = 600; // 600 px
}
final public function get_bildgroesse() { return $this->bildgroesse; }
final public function get_bildhoehe() { return $this->bildhoehe; }
final public function get_bildbreite() { return $this->bildbreite; }
// Gibt die Ausmaße in Megapixel aus:
final function get_megapixel()
{
return round($this->bildghoehe * $this->bildbreite / 1000000);
}
// Gibt die Höhe aus, die ein Thumbnail bei der Bildgröße haben würde
final function get_thumbnail_height()
{
return intval($this->breite_thumbnail * $this->bildhoehe / $this->bildbreite);
}
}
?>
Die Klasse kommt in den Ordner „Include/Klassen/“ mit dem Dateinamen „bild.class.php“
Wir sehen hier das konstante Element „$breite_thumbnail“ - es ist public, da eine Änderung des Wertes ausgeschlossen ist.
Die Klasse „Bild“ repräsentiert die Daten eines Bildes. Es ist unumgänglich, den eindeutigen Sinn einer Klasse vorab zu definieren. Die Versuchung könnte hier bspw. sein, dass man eine Methode schreiben würde, um gleich einen Thumbnail zu erzeugen. Das gefällt allerdings der Klasse „Bild“, die die
Daten eines Bildes verwalten soll gar nicht, die Überlegung wäre nicht passend. Eine Lösung dafür wäre eine globale Funktion, die ein Objekt der Klasse „Bild“ erwartet (Ausgansbild), und als Rückgabewert ebenfalls ein „Bild“-Objekt von dem erzeugten Thumbnail liefert:
PHP-Code:
<?php
// bild.func.php
// Erstellt einen Thumbnail
function create_thumbnail(Bild $ausgangsbild) // Ein neues Feature in PHP 5 - Type Hinting
{
// Wir erstellen hier lediglich ein neues Objekt, ohne tatsächlich ein Bild zu erstellen
$breite = $ausgangsbild->breite_thumbnail;
$hoehe = $ausgangsbild->get_thumbnail_height();
$name = 'bezeichnung_neues_bild.jpeg';
// Hier würde man das neue Bild erstellen
return new Bild($name);
}
?>
So ähnlich machen wir das mit der Registrierung eines neuen Users. Wir schreiben eine Funktion, die die benötigten Daten in die Datenbank aufnimmt. Es ist unsinnig, unlogisch für einen einzelnen Vorgang eine Klasse zu entwerfen. Der Rückgabewert dieser Funktion wird ebenfalls ein Objekt der Klasse „User“ sein:
PHP-Code:
<?php
// user.func.php
// Erstellt einen neuen User
function register_user($name, $passwort, $rang)
{
global $db_name;
global $db_pass;
global $db_rang;
// Die ID herausfinden
$neue_id = sizeof($db_name);
// Hier befüllen wir unsere Datenbank:
$db_name[$neue_id] = $name;
$db_pass[$neue_id] = $passwort;
$db_rang[$neue_id] = $rang;
return new User($neue_id);
}
?>
Die beiden Funktionen kommen in den Ordner „Include/Funktionen/“. Die Dateien heißen „bild.func.php“ und „user.func.php“.
Das, was wir bis jetzt haben reicht bereits für einige Operationen. Im nächsten Kapitel werden wir etwas mit der Schnittstelle scripten.
4 Die Benutzung der Schnittstelle
4.1 Bemerkungen
Die Dateien, die ich jetzt vorstelle sind als Demos gedacht. Sie sollen zeigen, wie unsere Schnittstelle benutzt werden kann. Es wird ein Script zur Regisitrierung und eins zum Überprüfen des Rangs vorgestellt.
4.2 Registrierung eines Users
PHP-Code:
<?php
// register.php
// Die Datenbank:
require_once "Include/db.inc.php";
// Klasse „User“
require_once "Include/Klassen/user.class.php";
// Funktion „User“
require_once "Include/Funktionen/user.func.php";
if (isset($_POST['submit']))
{
$user = register_user($_POST['name'], $_POST['pass'], 0); // Rang = 0 für einen neuen Standard-User
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
</head>
<body>
<p>Danke für die Registrierung. Deine Daten sind:</p>
<p>Name: <?php echo $user->get_name(); ?></p>
<p>Passwort: <?php echo $user->get_passwort(); ?></p>
</body>
</html>
<?php }
else
{
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
</head>
<body>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<p>Name: <input type="text" name="name" /></p>
<p>Passwort: <input type="text" name="pass" /></p>
<input type="submit" name="submit" />
</form>
</body>
</html>
<?php } ?>
4.3 Das Profil eines Users checken
PHP-Code:
<?php
// check.php
// Die Datenbank:
require_once "Include/db.inc.php";
// Klasse „User“ und „Rang“
require_once "Include/Klassen/user.class.php";
require_once "Include/Klassen/rang.class.php";
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
</head>
<body>
<h1>Prüfe hier, welchen Rang ein User hat</h1>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<p>Name: <input type="text" name="name" /></p>
<input type="submit" name="submit" />
</form>
<p><?php
if (isset($_POST['submit']))
{
// ID suchen:
if (($id = array_search($_POST['name'], $db_name)) === false)
{
echo '<p>Benutzername nicht vorhanden</p>';
}
else
{
$user = new User($id);
$rang = new Rang($user->get_rang());
echo '<p>Darf '.$user->get_name().' ein Avatar besitzen? <b>'.($rang->avatar_upload() ? 'Ja' : 'Nein').'</b></p>';
echo '<p>Darf '.$user->get_name().' große Bilder hochladen? <b>'.($rang->big_pictures_upload() ? 'Ja' : 'Nein').'</b></p>';
}
}
?></p>
</body>
</html>
Hinweis: $db_name aus „db.inc.php“ hat die ganzen Namen, die in Frage kämen, gespeichert.
4.4 Zusammenfassung
Ich habe nun zwei Scripte erstellt, die die Schnittstelle benutzen. Bitte vergleiche die Übersicht des Codes mit einem möglichen Code ohne OOP. Stelle vor, wie leicht man mit diesen Objekten hantieren kann, ohne jedes mal Gedanken über eine Problemlösung zu machen, die dann meistens auch nur einmal benötigt wird. Ferner achte auch auf die Fehler, die hier ohne OOP leicht entstehen könnten.
Das Ende dieses Kapitels ist auch das Ende der Unterrichtsstunde. Demnächst geht es um eine Erweiterung unserer Benutzerschnittstelle, wo jeder mit seinen Ideen mitwirken kann. Zuvor gibt es eine Art Hausaufgabe, die Du zur Übung alleine lösen solltest.
5 Zum Üben
5.1 Login
Zum Einloggen brauchen wir natürlich die Datenbank und die Klasse „User“ einzubinden. Wie man anhand eines Namens die ID herausfindet, kann man der „check.php“ entnehmen. Man merkt, dass die ID oft gebraucht wird, man könnte dafür eine Funktion schreiben. Die Aufgabe lautet also eine Funktion fürs Einloggen und eine, die die ID liefert.
5.2 Profil bearbeiten
Profil bearbeiten, mit anderen Worten das Ändern der Daten wie Name, Passwort (dafür haben wir eine Funktion), Avatar.
5.3 Die Bilder
Mit Bildern wurde in der Demo nichts gemacht. Du brauchst keine echten Bilder hochladen; es reicht, wenn Du mit Klassen rumspielst und die Ausgaben notierst.
5.4 Admin-Bereich
Was mit dem Admin-Bereich gemeint ist, ist jedem klar. Der Rang-Wert bei einem Admin ist sagen wir mal 4.
6 Deine Beteiligung
6.1 Richtlinien
Wie schon erwähnt, kann sich jeder an dem Projekt beteiligen. Wichtig ist, dass man es mit/wegen OOP macht. Spielereien wie das Einbinden einer SQL-Datenbank oder Ähnliches sind hier also fehl am Platz. Es sind Klassen, Funktionen zu entwerfen, die unsere Schnittstelle mächtiger machen. Es dient einfach als Training.
Vorgestellt können die Erweiterungen gleich hier. Möglichst gut strukturiert und dokumentiert, damit jeder in deine Arbeit schnell reinsteigen und diese beurteilen kann.
Im Anhang findest Du alle Dateien, die hier vorgekommen sind.
7 Hilfreiche Links zum Thema OOP (bei PHP 5)
http://www.php.net/manual/de/language.oop5.php (etwas schwierig für blutige Anfänger)
http://www.professionelle-softwareen...5.de/index.php
...
OOP allgemein
http://de.wikipedia.org/wiki/Oop
...
Falls jemand interessante Links über OOP in Verbindung mit PHP 5 kennt, erweitere ich die Liste gerne.
8 Schlusswort
Ich hoffe, dass dieser Artikel etwas Hilfe für die zukünftige Arbeit mit OOP-Projekten bereitet hat. Entwickelt wie ich schon gesagt habe ähnliche Projekte, damit die Handhabung besser wird. Es ist einfach Übung erforderlich, weshalb ich auch auf die Weiterentwicklung
von euch bestehe.