logo

Extra Block Types (EBT) - Neue Erfahrung im Layout Builder❗

Extra Block Types (EBT) - gestylte, anpassbare Blocktypen: Diashows, Registerkarten, Karten, Akkordeons und viele andere. Eingebaute Einstellungen für Hintergrund, DOM Box, Javascript Plugins. Erleben Sie die Zukunft der Layouterstellung schon heute.

Demo EBT-Module EBT-Module herunterladen

❗Extra Absatztypen (EPT) - Erfahrung mit neuen Absätzen

Extra Paragraph Types (EPT) - analoger, auf Absätzen basierender Satz von Modulen.

Demo EPT-Module EPT-Module herunterladen

Scroll

1.5. Klassen für die Arbeit mit Datenbank und Templates einbinden

26/05/2025, by Ivan

Wir haben die Struktur für unser Framework erstellt, jetzt ist es Zeit, über die Speicherung von Daten wie Nachrichten und Produkten nachzudenken. Das Objekt für die Arbeit mit der Datenbank sollte folgende Fähigkeiten besitzen:

  • Verwaltung der Verbindung zur Datenbank
  • Bereitstellung einer kleinen Abstraktion von der Datenbank
  • Caching von Abfragen
  • Vereinfachung allgemeiner Datenbankoperationen

Dafür erstellen wir das Objekt Registry/objects/db.class.php:

<?php

/**
 * Datenbankverwaltung
 * Bietet eine kleine Abstraktion von der Datenbank
 */
class database {

  /**
   * Ermöglicht mehrere Verbindungen zur Datenbank
   * wird selten benutzt, kann aber nützlich sein
   */
  private $connections = array();

  /**
   * Aktive Verbindung
   * setActiveConnection($id) erlaubt das Ändern der aktiven Verbindung
   */
  private $activeConnection = 0;

  /**
   * Gespeicherte Abfragen für spätere Verwendung
   */
  private $queryCache = array();

  /**
   * Gespeicherte Daten für spätere Verwendung
   */
  private $dataCache = array();

  /**
   * Letzte Abfrage
   */
  private $last;

  /**
   * Konstruktor
   */
  public function __construct()
  {

  }

  /**
   * Neue Verbindung erstellen
   * @param String Hostname der Datenbank
   * @param String Benutzername
   * @param String Passwort
   * @param String Datenbankname
   * @return int ID der neuen Verbindung
   */
  public function newConnection( $host, $user, $password, $database )
  {
    $this->connections[] = new mysqli( $host, $user, $password, $database );
    $connection_id = count( $this->connections ) - 1;
    if( mysqli_connect_errno() )
    {
      trigger_error('Fehler bei der Verbindung: '.$this->connections[$connection_id]->error, E_USER_ERROR);
    }

    return $connection_id;
  }

  /**
   * Aktive Verbindung schließen
   */
  public function closeConnection()
  {
    $this->connections[$this->activeConnection]->close();
  }

  /**
   * Aktive Verbindung ändern
   * @param int ID der neuen Verbindung
   */
  public function setActiveConnection( int $new )
  {
    $this->activeConnection = $new;
  }

  /**
   * Anfrage im Cache speichern
   * @param String SQL-Abfrage
   * @return int Zeiger im Cache
   */
  public function cacheQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Fehler bei der Abfrage: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
      return -1;
    }
    else
    {
      $this->queryCache[] = $result;
      return count($this->queryCache) - 1;
    }
  }

  /**
   * Anzahl Zeilen aus Cache
   * @param int Cache-ID
   * @return int Anzahl Zeilen
   */
  public function numRowsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->num_rows;
  }

  /**
   * Ergebnisse aus Cache holen
   * @param int Cache-ID
   * @return array Zeile
   */
  public function resultsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Daten im Cache speichern
   * @param array Daten
   * @return int Cache-Zeiger
   */
  public function cacheData( $data )
  {
    $this->dataCache[] = $data;
    return count( $this->dataCache ) - 1;
  }

  /**
   * Daten aus Cache holen
   * @param int Cache-ID
   * @return array Daten
   */
  public function dataFromCache( $cache_id )
  {
    return $this->dataCache[$cache_id];
  }

  /**
   * Datensätze löschen
   * @param String Tabelle
   * @param String Bedingung
   * @param int Limit Anzahl der zu löschenden Zeilen
   */
  public function deleteRecords( $table, $condition, $limit )
  {
    $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    $this->executeQuery( $delete );
  }

  /**
   * Datensätze aktualisieren
   * @param String Tabelle
   * @param array Änderungen Feld => Wert
   * @param String Bedingung
   * @return bool
   */
  public function updateRecords( $table, $changes, $condition )
  {
    $update = "UPDATE " . $table . " SET ";
    foreach( $changes as $field => $value )
    {
      $update .= "`" . $field . "`='{$value}',";
    }

    $update = substr($update, 0, -1);
    if( $condition != '' )
    {
      $update .= " WHERE " . $condition;
    }

    $this->executeQuery( $update );

    return true;
  }

  /**
   * Datensätze einfügen
   * @param String Tabelle
   * @param array Daten Feld => Wert
   * @return bool
   */
  public function insertRecords( $table, $data )
  {
    $fields  = "";
    $values = "";

    foreach ($data as $f => $v)
    {
      $fields  .= "`$f`,";
      $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";
    }

    $fields = substr($fields, 0, -1);
    $values = substr($values, 0, -1);

    $insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
    $this->executeQuery( $insert );
    return true;
  }

  /**
   * Query ausführen
   * @param String SQL-Abfrage
   */
  public function executeQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Fehler bei der Abfrage: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
    }
    else
    {
      $this->last = $result;
    }
  }

  /**
   * Zeilen der letzten Abfrage holen
   * @return array
   */
  public function getRows()
  {
    return $this->last->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Anzahl betroffener Zeilen der letzten Abfrage
   * @return int
   */
  public function affectedRows()
  {
    return $this->connections[$this->activeConnection]->affected_rows;
  }

  /**
   * Daten bereinigen (Sicherheitscheck)
   * @param String Eingabedaten
   * @return String bereinigte Daten
   */
  public function sanitizeData( $data )
  {
    return $this->connections[$this->activeConnection]->real_escape_string( $data );
  }

  /**
   * Destruktor, schließt alle Verbindungen
   */
  public function __destruct()
  {
    foreach( $this->connections as $connection )
    {
      $connection->close();
    }
  }
}
?>

Bevor wir zur Datenbankverbindung kommen, sehen wir uns an, was unser Klasse macht. Wir können einfache Operationen wie Einfügen, Aktualisieren und Löschen über Methoden der Klasse durchführen:

// Einfügen
$registry->getObject('db')->insertRecords( 'products', array('name'=>'Tasse' ) );
// Aktualisieren
$registry->getObject('db')->updateRecords( 'products', array('name'=>'Rote Tasse' ), 'ID=2' );
// Löschen
$registry->getObject('db')->deleteRecords( 'products', "name='Rote Tasse'", 5 );

Unsere Klasse unterstützt außerdem Caching.

Als nächstes fügen wir ein weiteres Objekt für das Template-Management hinzu: Registry/objects/template.class.php

<?php

// Konstante aus index.php um Aufruf außerhalb des Frameworks zu verhindern
if ( ! defined( 'FW' ) )
{
  echo 'Diese Datei darf nur aus index.php aufgerufen werden.';
  exit();
}

/**
 * Template-Klasse
 */
class template {

  private $page;

  /**
   * Konstruktor
   */
  public function __construct()
  {
    include( APP_PATH . '/Registry/objects/page.class.php');
    $this->page = new Page();
  }

  /**
   * Fügt einen Template-Teil ein
   * @param String $tag Tag, wo das Template eingefügt wird, z.B. {hello}
   * @param String $bit Template-Pfad
   */
  public function addTemplateBit( $tag, $bit )
  {
    if( strpos( $bit, 'Views/' ) === false )
    {
      $bit = 'Views/Templates/' . $bit;
    }
    $this->page->addTemplateBit( $tag, $bit );
  }

  /**
   * Fügt alle Template-Teile in die Seite ein
   */
  private function replaceBits()
  {
    $bits = $this->page->getBits();
    foreach( $bits as $tag => $template )
    {
      $templateContent = file_get_contents( $template );
      $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() );
      $this->page->setContent( $newContent );
    }
  }

  /**
   * Ersetzt Tags mit Inhalten
   */
  private function replaceTags()
  {
    $tags = $this->page->getTags();
    foreach( $tags as $tag => $data )
    {
      if( is_array( $data ) )
      {
        if( $data[0] == 'SQL' )
        {
          $this->replaceDBTags( $tag, $data[1] );
        }
        elseif( $data[0] == 'DATA' )
        {
          $this->replaceDataTags( $tag, $data[1] );
        }
      }
      else
      {
        $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
        $this->page->setContent( $newContent );
      }
    }
  }

  /**
   * Ersetzt DB-Tags mit Daten aus der Datenbank
   */
  private function replaceDBTags( $tag, $cacheId )
  {
    $block = '';
    $blockOld = $this->page->getBlock( $tag );

    while ($tags = Registry::getObject('db')->resultsFromCache( $cacheId ) )
    {
      $blockNew = $blockOld;
      foreach ($tags as $ntag => $data)
      {
        $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( '' . $blockOld . '', $block, $pageContent );
    $this->page->setContent( $newContent );
  }

  /**
   * Ersetzt Daten-Tags mit gecachten Daten
   */
  private function replaceDataTags( $tag, $cacheId )
  {
    $block = $this->page->getBlock( $tag );
    $blockOld = $block;
    while ($tags = Registry::getObject('db')->dataFromCache( $cacheId ) )
    {
      foreach ($tags as $tag => $data)
      {
        $blockNew = $blockOld;
        $blockNew = str_replace("{" . $tag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( $blockOld, $block, $pageContent );
    $this->page->setContent( $newContent );
  }

  /**
   * Holt die Seite
   */
  public function getPage()
  {
    return $this->page;
  }

  /**
   * Baut Inhalt aus mehreren Templates
   */
  public function buildFromTemplates()
  {
    $bits = func_get_args();
    $content = "";
    foreach( $bits as $bit )
    {
      if( strpos( $bit, 'skins/' ) === false )
      {
        $bit = 'Views/Templates/' . $bit;
      }
      if( file_exists( $bit ) )
      {
        $content .= file_get_contents( $bit );
      }
    }
    $this->page->setContent( $content );
  }

  /**
   * Wandelt Datenarray in Tags um
   */
  public function dataToTags( $data, $prefix )
  {
    foreach( $data as $key => $content )
    {
      $this->page->addTag( $key.$prefix, $content);
    }
  }

  /**
   * Parst den Titel
   */
  public function parseTitle()
  {
    $newContent = str_replace('', '<title>'. $this->page->getTitle(), $this->page->getContent() );
    $this->page->setContent( $newContent );
  }

  /**
   * Verarbeitet die Ausgabe, ersetzt Tags und Tokens
   */
  public function parseOutput()
  {
    $this->replaceBits();
    $this->replaceTags();
    $this->parseTitle();
  }

}
?>
</pre>

<p>Wir haben auch die Klasse <code>Page</code> definiert, die im Template-Manager verwendet wird. Diese befindet sich in <code>Registry/objects/page.class.php</code>:</p>

<pre>
<?php

/**
 * Unsere Seitenklasse
 * Ermöglicht Funktionen wie passwortgeschützte Seiten, Einbindung von JS/CSS usw.
 */
class page {

  private $css = array();
  private $js = array();
  private $bodyTag = '';
  private $bodyTagInsert = '';

  // zukünftige Funktionen
  private $authorised = true;
  private $password = '';

  // Seitenelemente
  private $title = '';
  private $tags = array();
  private $postParseTags = array();
  private $bits = array();
  private $content = "";

  /**
   * Konstruktor
   */
  function __construct() { }

  public function getTitle()
  {
    return $this->title;
  }

  public function setPassword( $password )
  {
    $this->password = $password;
  }

  public function setTitle( $title )
  {
    $this->title = $title;
  }

  public function setContent( $content )
  {
    $this->content = $content;
  }

  public function addTag( $key, $data )
  {
    $this->tags[$key] = $data;
  }

  public function getTags()
  {
    return $this->tags;
  }

  public function addPPTag( $key, $data )
  {
    $this->postParseTags[$key] = $data;
  }

  /**
   * Holt die Tags nach der Verarbeitung
   */
  public function getPPTags()
  {
    return $this->postParseTags;
  }

  /**
   * Fügt einen Template-Tag hinzu
   */
  public function addTemplateBit( $tag, $bit )
  {
    $this->bits[ $tag ] = $bit;
  }

  /**
   * Holt alle Template-Tags
   */
  public function getBits()
  {
    return $this->bits;
  }

  /**
   * Findet einen Block im Seiteninhalt
   */
  public function getBlock( $tag )
  {
    preg_match ('#<!-- START '. $tag . ' -->(.+?)<!-- END '. $tag . ' -->#si', $this->content, $matches);

    $block = str_replace ('<!-- START '. $tag . ' -->', "", $matches[0]);
    $block = str_replace ('<!-- END '  . $tag . ' -->', "", $block);

    return $block;
  }

  public function getContent()
  {
    return $this->content;
  }

}
?>
</pre>

<p>Nachdem wir die Klassen für Datenbank und Templates erstellt haben, verbinden wir diese Klassen nun.</p>

<p>Wir erstellen eine Methode <code>storeCoreObjects()</code> in <code>Registry/registry.class.php</code>:</p>

<pre>
    public function storeCoreObjects()
    {
      $this->storeObject('database', 'db' );
      $this->storeObject('template', 'template' );
    }
</pre>

<p>Hier definieren wir, welche Klassen geladen werden sollen.</p>

<p>Jetzt erstellen wir noch eine Tabelle <code>users</code> mit Feldern <code>id</code>, <code>name</code> und <code>email</code>. Für ein Beispiel habe ich eine SQL-Datei im GitHub.</p>

<p>Um die Startseite anzuzeigen, erstellen wir ein Template <code>Views/Templates/main.tpl.php</code>:</p>

<pre>
<html>
<head>
    <title>Powered by PCA Framework</title>
</head>
<body>
<h1>Unsere Mitglieder</h1>
<p>Hier ist eine Liste unserer Mitglieder:</p>
<ul>
<!-- START users -->
<li>{name} {email}</li>
<!-- END users -->
</ul>
</body>
</html>
</pre>

<p>Wir haben den Tag <code>users</code> und die Tokens <code>{name}</code>, <code>{email}</code> definiert. In einem späteren Artikel werden wir den Template-Mechanismus detailliert besprechen. Nun gehen wir zurück zur <code>index.php</code> und verbinden Datenbank und Template.</p>

<p>Die <code>index.php</code> sieht nun so aus:</p>

<pre>
<?php
session_start();

error_reporting(E_ALL);

define( "APP_PATH", dirname( __FILE__ ) ."/" );
define( "FW", true );

function __autoload( $class_name )
{
    require_once('Controllers/' . $class_name . '/' . $class_name . '.php' );
}

require_once('Registry/registry.class.php');
$registry = Registry::singleton();

$registry->storeCoreObjects();

$registry->getObject('db')->newConnection('localhost', 'root', '', 'framework');

$registry->getObject('template')->buildFromTemplates('main.tpl.php');

$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users');

$registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) );

$registry->getObject('template')->getPage()->setTitle('Unsere Benutzer');

$registry->getObject('template')->parseOutput();

print $registry->getObject('template')->getPage()->getContent();

print $registry->getFrameworkName();

exit();

?>
</pre>

<p>Wenn alles korrekt ist und Benutzer in der Datenbank sind, wird eine Liste wie im Screenshot angezeigt.</p>

<p>Falls Fehler auftreten, können Sie den Code auf GitHub prüfen, da ich dort die funktionierende Version hinterlegt habe.</p>

<p>Folgende Fehler traten bei mir beim Schreiben des Artikels auf:</p>

<p>Klassenname für die DB-Klasse in <code>Registry/objects/db.class.php</code> ändern:</p>

<pre>
-class database {
+class db {
</pre>

<p>Statische Methoden definieren und Klasse in <code>Registry/registry.class.php</code> anpassen:</p>

<pre>
-    public function storeObject( $object, $key )
+    public static function storeObject( $object, $key )
-        require_once('objects/' . $object . '.class.php');
+        require_once('Registry/objects/' . $object . '.class.php');
-        self::$objects[ $key ] = new $object( self::$instance );
+        self::$objects[ $key ] = new $object( self::$instance );
</pre>

<p>Controller für DB hinzufügen:</p>

<blockquote>
<p>Controllers/db/<br />
Controllers/db/db.php</p>
</blockquote>

<p>Fehler in Template-Klasse <code>Registry/objects/template.class.php</code> korrigieren:</p>

<pre>
-    $newContent = str_replace('<title>', '<title>'. $this->$page->getTitle(), $this->page->getContent() );
+    $newContent = str_replace('<title>', '<title>'. $this->page->getTitle(), $this->page->getContent() );
</pre>