Marco: Dynamische Datenbankschnittstelle

Hallo,

ich studiere technische Informatik und muss nun für das Wahlpflichtfach Webdesign eine Datenbankschnittstelle mit PHP entwickeln. Allerdings habe ich diese Woche das erste Mal seit 3, 4 Jahren wieder was mit PHP programmiert und bin somit wohl wieder ein Anfänger.

Die Datenbankschnittstelle soll in einem Webprojekt verwendet werden und auf MySQL basieren. Diese Datenbankschnittstelle soll gegen eine andere getauscht werden können (beispielsweise eine die MS SQL Server unterstütz) ohne am Quellcode etwas verändern zu müssen.

Diese Datenbankschnittstelle soll darüber hinaus Objektorientiert sein, soll eine vernünftige Fehlerbehandlung enthalten und soll so "gut" wie möglich programmiert werden, denn schließlich wird diese ja auch von meinem Professor bewertet. Den folgenden Code habe ich bisher mal entwickelt:

/*************** abstrakte Datenbank Schnittstelle ***************/

<?php

abstract class DatabaseConnection
   {
      # Protected Variablen
      protected $connection = NULL;
      protected $host = NULL;
      protected $database = NULL;
      protected $user = NULL;
      protected $password = NULL;

# Abstrakte Funktionen, Definitionen
      public abstract function Connect($host, $user, $password);
      public abstract function Disconnect();
      ...
   }
?>

/*************** MySQL Datenbank Schnittstelle ***************/

<?php

require_once('DatabaseConnection.php');
   require_once('ExtendedException.php');

class MySQLDatabaseConnection extends DatabaseConnection
   {
      # Public Interface
      public function Connect($host, $user, $password)
      {
         $this->host = $host;
         $this->user = $user;
         $this->password = $password;

$this->connection = @mysql_connect($host, $user, $password, true);
         if ($this->connection == false)
         {
            $exception = new DatabaseConnectionException($host, $user, $password);
            throw $exception;
         }
      }
   }
?>

/*************** User definded exceptions ***************/

<?php

class DatabaseConnectionException extends ExtendedException
   {
      public function __construct($host, $user, $password)
      {
         if (!$this->showPassword) $password = "*****";

$this->message = "Fehler beim Herstellen der Datenbankverbindung" . ZEILENUMBRUCH .
                          "Host: " . $host . $this->ZEILENUMBRUCH .
                          "User: " . $user . ZEILENUMBRUCH .
                          "Password: " . $password;
      }
   }
?>

Vielen Dank für Eure Hilfe

  1. echo $begrüßung;

    ich studiere technische Informatik und muss nun für das Wahlpflichtfach Webdesign eine Datenbankschnittstelle mit PHP entwickeln. Allerdings habe ich diese Woche das erste Mal seit 3, 4 Jahren wieder was mit PHP programmiert und bin somit wohl wieder ein Anfänger.

    Aktuelle PHP-Versionen enthalten PDO, das das gleiche Zeil verfolgt. Macht aber nichts, du sollst es ja zu Übungszwecken neu erfinden.

    Die Datenbankschnittstelle soll in einem Webprojekt verwendet werden und auf MySQL basieren. Diese Datenbankschnittstelle soll gegen eine andere getauscht werden können (beispielsweise eine die MS SQL Server unterstütz) ohne am Quellcode etwas verändern zu müssen.

    Dieser Wunsch bietet in meinen Augen nur den einen genannten Vorteil, hat aber mit einigen nicht unerheblichen Nachteilen zu leben. Beispielsweise ist man damit auch auf einen minimalen SQL-Wortschatz beschränkt und kann spezielle Leistungsmerkmale, Funktionen und Statements der zugrunde liegenden DBMS nicht ausnutzen. Teilweise sind auch Funktionalitäten in den verschiedenen DBMS nicht kompatibel zueinander. Während MySQL auto_increment kennt, muss man bei Oracle Sequenzen verwenden, usw. usf. Auf der anderen Seite steht der ziemlich selten auftretende Fall eines DBMS-Wechsels.

    Nach meiner Erfahrung ist es besser keine Schnittstelle zu einem DBMS zu implementieren sondern eine zu den Daten. Das heißt, die Anwendung fragt nach Daten und übergibt solche. Wo diese herkommen bzw. hingehen, interessiert die Anwendung nicht weiter. Sie muss sich nicht mit SQL-Statements und dergleichen rumschlagen. Selbst ob überhaupt ein DBMS dahintersteckt, kann ihr egal sein.
    Die Daten-Schicht kann ihrerseits auf die volle Funktionalität eines speziellen DBMS zugreifen. Auch proprietäre SQL-Bestandteile können Verwendung finden. Auch mehrere (auch verschiedene) DBMS oder andere Datenhaltungen (XML, Dateien) können Anwendung finden.

    Aber das nur nebenbei, du hast ja deine Übungsaufgabe.

    Diese Datenbankschnittstelle soll darüber hinaus Objektorientiert sein, soll eine vernünftige Fehlerbehandlung enthalten und soll so "gut" wie möglich programmiert werden, denn schließlich wird diese ja auch von meinem Professor bewertet. Den folgenden Code habe ich bisher mal entwickelt:

    Und was ist nun deine Frage? Soll ich Anmerkungen zu deinem Code geben?

    /*************** abstrakte Datenbank Schnittstelle ***************/

    <?php

    abstract class DatabaseConnection
       {
          # Protected Variablen

    Das diese protected sind sieht man. Wenn du was kommentieren willst, kommentiere den Sinn, der hinter einem bestimmten Ding steht, nicht das aus dem Quelltext offensichtliche.

    protected $connection = NULL;
          protected $host = NULL;
          protected $database = NULL;
          protected $user = NULL;
          protected $password = NULL;

    # Abstrakte Funktionen, Definitionen
          public abstract function Connect($host, $user, $password);

    An welcher Stelle wird der Wert für $database übergeben? Einige DBMS (bzw. DBMS-APIs) benötigen sie bereits beim Verbinden, andere erst später. Für erstere muss, für letzere sollte das Auswählen der Datenbank ebenfalls in Connect() erfolgen, der Einheitlichkeit wegen.

    Warum merkst du dir überhaupt Host, Username und Password, wenn sie Pflichtparameter bei Connect() sind und ansonsten (vermutlich) nicht weiter gebraucht werden?

    $this->connection = @mysql_connect($host, $user, $password, true);

    Wenn du schon ein modernes PHP voraussetzt, solltest du auch gleich auf die modernere mysqli-Extension verwenden. (Lass dich nicht von der "Experimentell"-Warnung in der deutschen Übersetzung irreführen, die gilt schon seit langem nicht mehr.)

    if ($this->connection == false)
             {
                $exception = new DatabaseConnectionException($host, $user, $password);
                throw $exception;

    Wozu die extra Variable? Du brauchst sie nicht weiter, kannst also "throw new Exception..." als Einzeiler notieren.

    class DatabaseConnectionException extends ExtendedException
       {
          public function __construct($host, $user, $password)
          {
             if (!$this->showPassword) $password = "*****";

    Befindet sich eine Definition (Deklaration plus Wertzuweisung) von $this->showPassword in ExtendedException? Wenn ja, kannst du daraus auch eine (Klassen-)Konstante machen, denn man hat sowieso keine Chance, den Wert während der Laufzeit zu ändern. Jedenfalls nicht so, wie du das derzeit notiert hast.

    $this->message = "Fehler beim Herstellen der Datenbankverbindung" . ZEILENUMBRUCH .
                              "Host: " . $host . $this->ZEILENUMBRUCH .
                              "User: " . $user . ZEILENUMBRUCH .
                              "Password: " . $password;

    ZEILENUMBRUCH einmal mit, einmal ohne $this-> ? Außerdem ist es der einzige deutsche Bezeichner unter ansonsten englischen. Ich plädiere für einsprachige Bezeichner.

    Für wen ist eigentlich der Meldungstext gedacht? Der Endanwender kann mit den Informationen nichts anfangen, und der Systemadministrator hat sowieso Zugriff auf das Passwort.

    ?>

    Dieser Abschluss ist am Dateiende nicht nötig. Setzt man ihn, muss man achtgeben, keine Whitespace-Zeichen dahinter stehen zu lassen. Lässt man ihn weg, missfällt das zwar der Ordnungsliebe einiger, man hat aber eine "headers already sent"-Fehlerquelle weniger.

    echo "$verabschiedung $name";

    1. Hallo dedlfix,

      danke für deine ausführliche Antwort, hast bei einigen Dingen natürlich Recht. Bei anderen Punkten ist es so, dass ich nicht den kompletten Quelltext hier posten wollte, damit das Thema überschaubar bleibt.

      Danke für den tollen Link, werde mir das genau ansehen.

      Bin mir bei der Fehlerbehandlung noch etwas unsicher, ich weiß nicht genau wie ich das ganze handhaben soll. Soll ich user defined exceptions werfen, oder die normalen Meldungen ausgeben oder was ganz anderes?

      Hast du mir noch einen Link zu "einer Schnittstelle zu den Daten" wie du es genannt hast. Würde mir gerne noch ein bißchen mehr darüber ansehen.
      "Nach meiner Erfahrung ist es besser keine Schnittstelle zu einem DBMS zu implementieren sondern eine zu den Daten."

      LG Marco

      1. echo $begrüßung;

        Bin mir bei der Fehlerbehandlung noch etwas unsicher, ich weiß nicht genau wie ich das ganze handhaben soll. Soll ich user defined exceptions werfen, oder die normalen Meldungen ausgeben oder was ganz anderes?

        Exceptions zu werfen ist schon nicht verkehrt[1], nur solltest du den Fehlertext nicht im Hinblick auf die spätere Verwendung formatieren, sondern einfach nur die Informationen bereitstellen. Das In-Form-Bringen ist Aufgabe des catch-Codeteils eines try-catch-Konstrukts.

        "Nach meiner Erfahrung ist es besser keine Schnittstelle zu einem DBMS zu implementieren sondern eine zu den Daten."
        Hast du mir noch einen Link zu "einer Schnittstelle zu den Daten" wie du es genannt hast. Würde mir gerne noch ein bißchen mehr darüber ansehen.

        Ich weiß nicht, ob es dafür einen konkreten Namen oder Beispielimplementationen gibt. Es ist einfach nur meine Erfahrung, die sich aus der Anwendung von Datenbankabstraktionsschichten und objektorienten Interfaces zu Datenbanktabellen bisher ergab. Mit Tabelleninterfaces[2] lassen sich zwar Standard-Aufgaben schnell implementieren, doch sobald man Verknüpfungen zu anderen Tabellen braucht, oder auch berechnete Spalten oder Funktionen verwenden möchte, fangen die Verrenkungen an.[3]

        Meine "Datenschnittstelle" ist im Prinzip einfach nur eine Konzentration des Daten abfragenden und speichernden Codes in einem oder mehreren Objekten. Er liegt also an einer definierten Stelle und kann im eher unwahrscheinlichen Fall eines DBMS-Wechsels an die neuen Gegebenheiten angepasst werden. Die Schnittstelle zur Anwendungslogik kann je nach Bedarf so gestaltet sein, dass sie einzelne Werte oder eine Ansammlung (Array) übergibt.[4] In Richtung DBMS habe ich mehr oder weniger nur ein einfaches RUDI-Interface unter meiner "Datenschnittstelle" liegen. Die notwendigen SQL-Statements sind in meiner Schicht notiert oder werden da "endmontiert". Zu sehen gibt es das aber nicht öffentlich.[5]

        [1] In welchen Fällen eine Exception das richtige Mittel ist und wann nicht wird in Fachkreisen unterschiedlich bewertet. Doch das will ich nicht weiter vertiefen.
        [2] beispielsweise Zend_Db_Table aus dem Zend Framework
        [3] Diese Aufgaben sollte man vermutlich besser der Datenbank und einer View überlassen.
        [4] Mit Hilfe der SPL kann man statt des ressourcenaufwendigen Erstellens eines Arrays einen Iterator implementieren, der sich zwar wie ein Array mit foreach durchlaufen lässt, den sonst in den Array-Elementen enthaltenen Wert aber erst zur Abfragezeit ermittelt (z.b. Fetchen aus einer Ergebnismenge).
        [5] Dafür ist es mir einfach nicht sauber genug implementiert und weicht auch noch von Anwendung zu Anwendung erheblich voneinander ab - muss ja, jeder Anwendungsfall hat ja seine eigenen Anforderungen.

        echo "$verabschiedung $name";