Serie: Datenbankzugriff mit dem Zend Framework
- Teil 1: App-Namespace Inititalisierung
- Teil 2: Models anlegen und DB-Operationen
- Teil 3: Joins im Zend Framework
- Teil 4: Abstrakte Klasse zum Pimpen des Datenbankzugriffs
So, Finale. In diesem letzten Teil der Serie möchte ich euch eine Klasse zur Verfügung stellen, die das Leben mit Datenbanken im Zend Framework ein wenig vereinfacht. Es geht uns darum häufig verwandte Konstrukte in eine Klasse auszulagern, von der alle anderen Models erben. Somit erhält man als Entwickler schnelleren, intuitiveren oder auf eine andere Art vereinfachten Zugriff auf Lösungen für wiederkehrende Aufgaben.
Methode Numero Uno ist…
selectWithJoin()
public function selectWithJoin()
{
return $this->select()
->setIntegrityCheck(false);
}
Kommt euch bekannt vor? Sehr aufmerksam! Habe ich bereits im vorherigen Blogpost vorgestellt. Der Weg dorthin und der Nutzen wurden entsprechend ausführlich im letzten Artikel dargelegt. Also direkt weiter im Text.
Einzelnen Spaltenwert holen
Allzu oft will ich aus einer Abfrage nur den Wert der ersten (einzigen?) Spalte der ersten Ergebnisreihe extrahieren. Die Klasse Zend_Db_Adapter_Abstract
enthält eine solche Methode: fetchOne()
. Warum es diese nützliche Methode nicht auch in die Zend_Db_Table_Abstract
geschafft hat… keine Ahnung.
Jedenfalls habe ich mir kurzerhand ein Äquivalent dafür geschaffen:
public function fetchOne($query)
{
$res = parent::fetchRow($query);
if (count($res) < 1)
return false;
foreach ($res as $col) {
return $col;
}
}
Was bringt das?
// without 'fetchOne()'
$query = $this->select()
->from($this, 'color')
->where('name = ?', 'Maru');
$res = $this->fetchRow($query);
return $res['name'];
// using 'fetchOne()'
$query = $this->select()
->from($this, 'color')
->where('name = ?', 'Maru');
return $this->fetchOne($query);
Primärschlüssel-Datensatz ohne umschließende Array
Einen ähnlichen Vereinfachungsgrad bietet meine Implementierung der find()
-Methode. Will man im ZF einen Datensatz anhand eines Primärschlüssels erhalten, bietet sich diese Methode an. Jedoch gibt sie ein Zend_Db_Table_Rowset_Abstract
zurück, d.h. ein Array von Zeilen. In den meisten Fällen möchte man aber nur eine Zeile erhalten.
// using 'find()' as is
$res = $this->find($id);
return $res[0]['name'];
// using Enno-extended 'find()'
return $this->find($id);
Wie sieht die Implementierung meiner find()
-Methode aus?
public function find($keys)
{
$res = parent::find($keys);
if (is_array($keys))
return $res;
return $res[0];
}
Für den Fall, dass doch mehrere Werte übergeben werden sollen, liefert diese Funktion trotzdem noch ein Array von Zeilen zurück. Sie kann also wie gewohnt weiter benutzt werden, wenn nötig.
True oder false holen
Um mir das ständige rumgecaste zu ersparen, habe ich mir noch eine Methode gebaut, die mir einen einzelnen Bool’schen Wert aus einem Abfrageergebnis zurück gibt. Dank der oben bereits vorgestellten fetchOne()
-Methode wird lediglich die erste Spalte der ersten Zeile geholt und nach Boolean gacastet.
public function fetchBool($query)
{
return (bool)$this->fetchOne($query);
}
Nur bestimmte Spalten zurückgeben
Was bei der Verwendung von Zend_Db_Table_Abstract
ziemlich nervt und unelegant wirkt, ist das Auswählen bestimmter Spalten. Was ich meine:
$query = $this->select()
->from($this, array('id', 'color'))
->where('name = ?', 'Maru');
Ich weiß nicht, wie es euch geht, aber die notwendige Angabe von $this
geht mir gehörig auf den Senkel. Als ob es eine andere Möglichkeit gäbe als die aktuelle Tabelle (wird nämlich – wie ihr hoffentlich gelernt habt – erst durch setIntegrityCheck(false)
möglich).
Ich habe mir eine Methode gebaut…
public function selectColumns($cols)
{
return $this->select()->from($this, $cols);
}
… die mir diese Angabe nun erspart.
$query = $this->selectColumns(array('id', 'color'))
->where('name = ?', 'Maru');
Datenbankpräfix
Gerade bei Shared Hostern kann es passieren, dass dem motivierten Entwickler nicht so viele Datenbanken zur Verfügung stehen, wie er vielleicht gern hätte. Ausweg: alle Tabellen in eine Datenbank schmeißen. Um sie trotzdem unterscheiden zu können und Namenskollisionen zu vermeiden, sollte man an dieser Stelle auf Präfixe zurückgreifen, z.B. senaeh_cats o.ä.
Um möglichst flexibel auf eine eventuell notwendige Änderung des Präfixes reagieren zu können und wiederverwendbaren Code zu haben, bietet sich dabei folgendes Vorgehen an: wir speichern das Präfix an einer zentralen Stelle, machen es global verfügbar und überschreiben den eigentlich gesetzten Tabellennamen. Doch eins nach dem anderen.
1. Präfix in die application.ini
In der application.ini könnt ihr zusätzlich zu den im zweiten Teil notierten Konfigurationsdaten noch das Präfix notieren.
resources.db.adapter = “pdo_mysql”
resources.db.prefix = “mein_praefix_”
resources.db.params.host = “localhost”
resources.db.params.username = “mein_benutzername”
resources.db.params.password = ‘mein_passwort’
resources.db.params.dbname = “mein_datenbank_name”
resources.db.isDefaultTableAdapter = true
2. Präfix in der Registry global verfügbar machen
Die Zend_Registry
ist der Container, in dem alle global verfügbaren Daten gespeichert werden können. Einfach die Bootstrap.php im application-Ordner um folgende Zeilen ergänzen:
protected function _initDbPrefix()
{
$opts = $this->getOption('resources');
$prefix = $opts['db']['prefix'];
Zend_Registry::set('Db_Prefix', $prefix);
}
3. Präfix über das abstrakte Model auf alle Models anwenden
Um dem ZF mitzuteilen, dass unseren durch $_name
gesetzten Tabellennamen noch ein Präfix vorangestellt werden muss, werden wir einmal mehr eine Methode überschreiben. In unsere abstrakte Model-Klasse, um die es in diesem Blogpost die ganze Zeit geht, fügen wir folgende Zeilen PHP-Kot ein:
protected function _setupTableName()
{
$this->_name = Zend_Registry::get('Db_Prefix') . $this->_name;
parent::_setupTableName();
}
Open-Source-ionierung
Statt die Datei irgendwo auf dem senäh-Server rumgammeln zu lassen, wird der Source-Code diesmal ganz cool auf GitHub gehostet. Für alle, die aus welchen Gründen auch immer eine Aversion gegen diese Plattform hegen, gibt es hier noch mal den kompletten Code zum Guttenbergen. Seid euch allerdings bewusst, dass es Kommentare, Readme und Updates ausschließlich bei der Octocat geben wird 😉
<?php
abstract class Application_Model_Abstract extends Zend_Db_Table_Abstract
{
protected function _setupTableName()
{
$this->_name = Zend_Registry::get('Db_Prefix') . $this->_name;
parent::_setupTableName();
}
public function getTableName()
{
return $this->_name;
}
public function find($keys)
{
$res = parent::find($keys);
if (is_array($keys))
return $res;
return $res[0];
}
public function fetchOne($query)
{
$res = parent::fetchRow($query);
if (count($res) < 1)
return false;
foreach ($res as $col) {
return $col;
}
}
public function fetchBool($query)
{
return (bool)$this->fetchOne($query);
}
public function selectColumns($cols)
{
return $this->select()->from($this, $cols);
}
public function selectWithJoin()
{
return $this->select()
->setIntegrityCheck(false);
}
}