php-icon

Objektorientierte Datenbankklassen

Vorwort

Heute werde ich ein kleines Tuturial geben wie man sich unter PHP jede menge Arbeit ersparen kann mit Hilfe einer Datenbankzugriffsklasse.

Wer zu Faul zum lesen ist oder mir lieber beim erklären lauschen und zusehen will, kann sich diesen Beitrag natürlich auch einfach hier auf dem Video anschauen (zum Downloaden meines Quellcodes müsst ihr aber trotzdem ans Ende dieses Beitrags scrollen).

Um den von mir hier geschriebenen Quellcode zu verwenden, wird PHP 5 benötigt.

Video

Video folgt…

Theorie

Ihr habt eine Hauptklasse, welche im folgenden als dbobject bezeichnet wird, welche Aufgaben wie Fetch(), InsertOrUpdate(), und Count() und noch einige andere enthält.

Außerdem habt ihr für jede Datenbanktabelle eine eigene, von dbobject abgeleitet, Klasse in welcher der Object und Tabellenaufbau definiert ist. Da wir jede Tabelle in einer eigenen Klasse noch einmal definieren bringt das sogar den Vorteil mit das ihr im falle einer zerstörten Datenbank, diese jederzeit mit Hilfe der Klassen wiederherstellen könnt (natürlich nur die Struktur, die Daten sind dann natürlich weg, wenn ihr kein Backup gemacht habt).

Zu guter Letzt benötigt ihr auch noch eine Datei für die Datenbankcurser mal proc.php nenne. Diese Datei enthält die SQL-Statements die bei bedarf parametrisiert abgerufen werden können.

Praxis

Hier mal am Beispiel eines Gästebuchs gezeigt:

Aufbau der dbobject.class.php:

<?php
$exceptionkeys = Array('procedure' ,'fetchnumber', 'fetcharray');
 
// Generiert eine zufällig alphanumereische ID der länge $length
function generateid($length)
{
  $newid='';
  $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  for ($i = 0; $i < $length; $i++)
  {
    $pos = rand(0, strlen($chars)-1);
    $newid .= $chars{$pos};
  }
  return $newid;
}
 
class dbobject
{
  // Liefert eine Prozedur aus der `proc` zurück.
  // Ausnahme: Wenn die Prozedur leer ist, so wird explizit ein Statement für den Tabellenindex zurück gegeben.
  function GetSQLProcedur()
  {
    if($this->GetProcedure() == '')
      return "SELECT * FROM `".get_class($this)."` WHERE `".get_class($this)."id` = '@1'";
    else
    {
      global $_proc;
      return $_proc[get_class($this).$this->GetProcedure()];
    }
  }
 
  // Führt einen Fetch auf die DB durch
  // Dabei muss das Statement in der `proc` wie folgt aussehen (Parameter werden mit @1-n gesetzt) (Statement darf KEIN LIMIT enthalten)
  // SELECT * FROM `table` [WHERE `id` = '@1'[...]]
  function Fetch()
  {
    global $_mysqli;
    global $exceptionkeys;
    $arg_list     = func_get_args();
    $querynumber  = 30;
    $fetchnumber  = $this->GetProcedure()!=''?$this->GetFetchnumber():0;
    if($fetchnumber%$querynumber==0)
    {
      $sql    = $this->GetSQLProcedur();
      for($i=0;$i<count($arg_list);$i++)
        $sql  = str_replace('@'.($i+1),$arg_list[$i],$sql);
      if(strpos($this->GetProcedure(), 'Delete')===false && strpos($this->GetProcedure(), 'Update')===false)
        $sql   .= ' LIMIT '.$fetchnumber.', '.$querynumber;
      $query  = $_mysqli->query($sql);
      if(strpos($this->GetProcedure(), 'Delete')===false && strpos($this->GetProcedure(), 'Update')===false)
      {
        if($query)
        {
          $result   = $query->fetch_all();
          $results  = count($result);
          for($i=$fetchnumber;$i<($results+$fetchnumber);$i++)
            $this->SetFetcharray($i,$result[$i-$fetchnumber]);
        }
        else
        {
          file_put_contents('dbobject_error_log', $sql);
          return false;
        }
      }
    }
    if($result = $this->GetFetcharray($fetchnumber++))
    {
      $i=0;
      foreach($this as $akey=>$aval)
      {
        if (in_array($akey, $exceptionkeys))
          continue;
        if(isset($result[$i]))
          $this->$akey  = $result[$i];
        $i++;
      }
      $this->SetFetchnumber($fetchnumber);
      return true;
    }
    else
      return false;
  }
 
  // Führt eine Suche in einer Tabelle aus und verknüpft alle übergebenen Paremeter mit AND-Verknüpfung.
  // Dabei muss die Reihenfolge, wie die Klasse/Tabelle aufgebaut ist, beachtet werden. 
  function SearchAND()
  {
    global $_mysqli;
    global $exceptionkeys;
    $arg_list     = func_get_args();
    $querynumber  = 30;
    $fetchnumber  = $this->GetFetchnumber();
    if($fetchnumber%$querynumber==0)
    {
      $sql = 'SELECT * FROM `'.get_class($this).'` WHERE';
      $args = count($arg_list)-1;
      $a    = -1;
      foreach($this as $akey=>$aval)
      {
        if($a++==$args)
          break; 
        if (in_array($akey, $exceptionkeys) || $arg_list[$a]===false)
          continue;
        $sql.= ' `'.$akey.'` = \''.$arg_list[$a].'\' AND';
      }
      if(strlen($sql) > 37)
        $sql = substr($sql, 0, -4);
      else
        $sql.= ' 1=1';
      $sql.= ' LIMIT '.$fetchnumber.', '.$querynumber;
      $query  = $_mysqli->query($sql);
      $result   = $query->fetch_all();
      $results  = count($result);
      for($i=$fetchnumber;$i<$results;$i++)
        $this->SetFetcharray($i,$result[$i]);
    }
    if($result = $this->GetFetcharray($fetchnumber++))
    {
      $i=0;
      foreach($this as $akey=>$aval)
      {
        if (in_array($akey, $exceptionkeys))
          continue;
        if(isset($result[$i]))
          $this->$akey  = $result[$i];
        $i++;
      }
      $this->SetFetchnumber($fetchnumber);
      return true;
    }
    else
      return false;
  }
 
  // Zählt die Elemente aus einer Prozedur
  // Dabei muss das Statement in der `proc` wie folgt aussehen (Parameter werden mit @1-n gesetzt) (Statement darf KEIN LIMIT enthalten)
  // SELECT COUNT(*) AS `quantity` FROM `table` [WHERE `id` = '@1'[...]]
  function Count()
  {
    global $_mysqli;
    $arg_list   = func_get_args();
    $sql        = $this->GetSQLProcedur();
    for($i=0;$i<count($arg_list);$i++)
      $sql  = str_replace('@'.($i+1),$arg_list[$i],$sql);
    $query      = $_mysqli->query($sql);
    if($result  = $query->fetch_object())
      return (double)$result->quantity;
    else
      return 0;
  }
 
  // Schreibt den aktuellen Datensatz in die Datenbank
  function InsertOrUpdate($fetch = true)
  {
    global $_mysqli;
    global $exceptionkeys;
    $classname  = get_class($this);
    $uniqueid   = $classname.'id';
    if(isset($this->$uniqueid))
    {
      $idlengh    = strlen($this->$uniqueid);
      for($tmpid=generateid($idlengh);count($_mysqli->query('SELECT * FROM `'.$classname.'` WHERE `'.$classname.'id` = \''.$tmpid.'\' LIMIT 1')->fetch_object())>0;$tmpid=generateid($idlengh));
    }
    $sql    = 'INSERT INTO `'.$classname.'` (';
    foreach($this as $akey=>$aval)
    {
      if (in_array($akey, $exceptionkeys))
        continue;
      $sql .= '`'.$akey.'`, ';
    }
    $sql    = substr($sql, 0, -2);
    $sql   .= ') ';
    $sql   .= 'VALUES (';
    foreach($this as $akey=>$aval)
    {
      if (in_array($akey, $exceptionkeys))
        continue;
      if($akey == $classname.'id'&&isset($this->$uniqueid))
        $sql .= '\''.$tmpid.'\', ';
      else
        $sql .= '\''.$this->$akey.'\', ';
    }
    $sql    = substr($sql, 0, -2);
    $sql   .= ') ';
    $sql   .= 'ON DUPLICATE KEY UPDATE ';
    foreach($this as $akey=>$aval)
    {
      if (in_array($akey, $exceptionkeys))
        continue;
      $sql .= '`'.$akey.'` = \''.$this->$akey.'\', ';
    }
    $sql    = substr($sql, 0, -2);
    if($_mysqli->query($sql) && isset($this->$uniqueid) && $fetch)
    {
      $emptystring  = str_repeat(" ", $idlengh);
      $indexid = $this->$uniqueid==$emptystring?$tmpid:$this->$uniqueid;
      $this->SetProcedure('');
      $this->Fetch($indexid);
      return true;
    }
    else
      return false;
  }
 
  // Löscht einen Datensatz aus der Datenbank
  function Delete($idvalue = '')
  {
    global $_mysqli;
    $id         = get_class($this).'id';
    $idvalue    = $idvalue != ''?$this->$id:$idvalue;
    $sql        = "DELETE FROM `".get_class($this)."` WHERE `".get_class($this)."id` = '".$idvalue."'";
    $_mysqli->query($sql);
  }
 
  // Sperrt alle Tabellen die in $tables stehen
  // Es müssen immer alle Tabellen gesperrt werden die während des Lock-Vorgangs benötigt werden
  function Lock($tables)
  {
    global $_mysqli;
    $sql        = "LOCK TABLES ".$tables." WRITE;";
    $_mysqli->query($sql);
  }
 
  // Gibt alle gesperrten Tabellen wieder frei
  function Unlock()
  {
    global $_mysqli;
    $sql        = "UNLOCK TABLES;";
    $_mysqli->query($sql);
  }
}
?>
Aufbau der proc.php:
proc.php   
<?php
$_proc['FetchAll'] = "SELECT * FROM `guestbook`";
?>
Aufbau der guestbook.class.php:
<?php
class guestbook extends dbobject
{
  // Datebankfelder
  public  $id           = 0;
  public  $guestbookid  = '          '; // ACHTUNG: Muss gefüllt sein!
  public  $name         = '';
  public  $email        = '';
  public  $comment      = '';
  public  $timestamp    = '';
 
  // Lokale Variablen
  public $procedure    = '';
  public $fetchnumber  = 0;
  public $fetcharray;
 
  // Getter und Setter für Datenbankfelder
  function GetId(){return $this->id;}
  function SetId($id){$this->id = $id;}
 
  function GetGuestbookid(){return $this->guestbookid;}
  function SetGuestbookid($guestbookid){$this->guestbookid = $guestbookid;}
 
  function GetName(){return $this->name;}
  function SetName($name){$this->name = $name;}
 
  function GetEmail(){return $this->email;}
  function SetEmail($email){$this->email = $email;}
 
  function GetComment(){return $this->comment;}
  function SetComment($comment){$this->comment = $comment;}
 
  function GetTimestamp(){return $this->timestamp;}
  function SetTimestamp($timestamp){$this->timestamp = $timestamp;}
 
  // Getter und Setter für Lokale Variablen
  function GetProcedure(){return $this->procedure;}
  function SetProcedure($procedure){$this->procedure = $procedure;$this->SetFetchnumber(0);}
 
  function GetFetchnumber(){return $this->fetchnumber;}
  function SetFetchnumber($fetchnumber){$this->fetchnumber = $fetchnumber;}
 
  function GetFetcharray($id){if(isset($this->fetcharray[$id])){return $this->fetcharray[$id];}{return false;}}
  function SetFetcharray($id,$fetcharray){$this->fetcharray[$id] = $fetcharray;}
 
  function __construct($guestbookid = '')
  {
    if($guestbookid!='')
      $this->Fetch($guestbookid);
    else
      $this->SetTimestamp(date('Y-m-d H:i:s'));
  }
}
?>
Aufbau der Tabelle guestbook im phpMyAdmin:
CREATE TABLE IF NOT EXISTS `guestbook` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `guestbookid` varchar(10) COLLATE utf8_bin NOT NULL,
  `name` varchar(50) COLLATE utf8_bin NOT NULL,
  `email` varchar(50) COLLATE utf8_bin NOT NULL,
  `comment` text COLLATE utf8_bin NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `guestbookid` (`guestbookid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1;
Aufbau der Ausgabedatei showguestbook.php:
<?php
require('proc.php');
require('dbobject.class.php');
require('guestbook.class.php');
 
// Datenbankverbindung herstellen
$_mysqli = new mysqli('localhost', 'BENUTZERNAME', 'PASSWORT', 'test');
 
if(isset($_REQUEST['job']))
{
  // Neuen Eintrag anlegen
  if($_REQUEST['job'] == 'anlegen')
  {
    $guestbook = new guestbook();
    $guestbook->SetName($_REQUEST['name']);
    $guestbook->SetEmail($_REQUEST['email']);
    $guestbook->SetComment($_REQUEST['text']);
    $guestbook->InsertOrUpdate();
  }
 
  // Eintrag entfernen
  if($_REQUEST['job'] == 'entfernen')
  {
    $guestbook = new guestbook();
    $guestbook->Delete($_REQUEST['guestbookid']);
  }
}
 
header('Content-Type: text/html; charset=utf-8');
echo  '<!DOCTYPE html>';
echo  '<html>';
echo    '<head>';
echo      '<title>Objektorientierte Datenbankklassen</title>';
echo    '</head>';
echo    '<body>';
echo      '<form action="?" method="post" style="border:solid 1px black;width:200px">';
echo        '<input type="hidden" name="job" value="anlegen">';
echo        'Name:<br><input name="name"><br>';
echo        'E-Mail:<br><input name="email"><br>';
echo        'Text:<br><textarea name="text"></textarea>';
echo        '<input type="submit" value="Absenden"><br>';
echo      '</form>';
 
// Alle Einträge anzeigen
$guestbook = new guestbook();
$guestbook->SetProcedure('FetchAll');
if($guestbook->Fetch())
{
  do
  {
    echo '<p>'.$guestbook->GetName().' ('.$guestbook->GetEmail().') schrieb am '.$guestbook->GetTimestamp().':<br>'.$guestbook->GetComment().'</p>';
  }while($guestbook->Fetch());
}
else
  echo  'Noch keine Einträge vorhanden!';
 
echo    '</body>';
echo  '</html>';
?>

Download

Den kompletten Code gibts natürlich auch hier nochmal zum Download:
Objektorientierte-Datenbankklassen.rar

Nachwort

Ihr seht also, wenn man sich erst einmal einen gewissen Grundstein gelegt hat, erspart einem das jede menge Arbeit.

Habt ihr noch Fragen oder Anregungen oder gar einen Fehler gefunden haben?
Dann schreibt mir doch einfach mal ein netten Kommentar hier zu diesem Beitrag.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload the CAPTCHA.