senäh

17senäh und so…

Zend Framework Artikelbild

Zend Framework
19. Jul 2012
Kommentare: 1

Models anlegen und DB-Operationen

Kategorien: Zend Framework | 19. Jul 2012 | Kommentare: 1

Serie: Datenbankzugriff mit dem Zend Framework

Nachdem ich die Serie vor knapp einem Monat angekündigt habe sollte nun doch mal der erste wirkliche Teil folgen, meint ihr nicht auch? Ok, sollt ihr haben. Ich werde euch erklären, wie ihr Models anlegt und wie die Datenbankoperationen im Zend Framework (ZF) umgesetzt sind.

Datenbankverbindung

Als erstes müssen wir die Zugangsdaten und die entsprechende Datenbank irgendwo hinterlegen. Bestens dafür geeignet ist die application.ini. Ihr wisst schon, die Datei, in der wie Senaeh als unseren Appnamespace deklariert haben 😉 Zur Sicherheit: zu finden gibt’s die Datei unter /application/configs/application.ini.

Folgende Daten tragt ihr dort ein:

resources.db.adapter = "pdo_mysql"
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

Fügt hier eure Daten an den entsprechenden Stellen ein (Benutzer, Passwort, Datenbank).

Als nächstes müssen wir die Datenbankverbindung mit den gerade festgehaltenen Daten herstellen. Müssen wir wirklich? Nein, müssen wir nicht. Das regelt das ZF automagisch für uns. Wir können also gleich zum Erstellen der Models übergehen.

Models erstellen

Jede Modelklasse, die wir anlegen, muss von der durch das ZF mitgelieferten Klasse Zend_Db_Table_Abstract erben. Legen wir also ein Model unter /application/models an und nennen die Datei Cats.php (wie sonst?). Zu Beginn sollte unser Model wie folgt aussehen:

<?php
class Senaeh_Model_Cats extends Zend_Db_Table_Abstract
{
}

Ihr seht, dass die Benennung der Klasse einer Konvention folgt: Namespace + Unterstrich + “Model” + Unterstrich + Modelname, bei uns also Senaeh_Model_Cats.

Was wir beim Erstellen des Models nicht vergessen sollten sind sowohl Name der zugehörigen Tabelle sowie dessen Primärschlüssel. Gehen wir also davon aus, dass unsere Tabelle auf den Namen cats hört und jede darin enthaltene Katze anhand einer cat_id identifiziert wird.

class Senaeh_Model_Cats extends Zend_Db_Table_Abstract
{
    protected $_name = 'cats';
    protected $_primary = 'cat_id';
}

Die Angabe des Primärschlüssels ist dabei nicht zwingend nötig, da das ZF in den meisten Fällen intelligent genug ist, diesen selbst zu finden.

Jetzt können wir das Model auch schon mit Methoden füllen, z.B.:

public function getAll()
{
    // fetch and return data
}

Mögliche Datenbank-Operationen

Ok, nun zum spaßigen Teil. Was können wir alles machen? Wie kann ich meine Anfragen zusammenstellen? Ich werde die Umsetzung folgender SQL-Befehle erklären:

  • SELECT
  • WHERE
  • INSERT
  • UPDATE
  • DELETE
  • LIMIT
  • ORDER
  • GROUP und
  • HAVING.

Abfragen mit select() erstellen

Die einfachste Abfrage ist wohl ein einfaches SELECT über die gesamte Tabelle.

// "SELECT `cats`.* FROM `cats`"
$query = $this->select();

Damit erstellen wir die Abfrage und speichern sie in der Variable $query. Warum? Weil wir sie später als Parameter übergeben, sobald wir Ergebnisse haben wollen.

Doch was, wenn wir nur bestimmte Spalten haben wollen?

// "SELECT `cats`.`cat_id`, `cats`.`cat_name` FROM `cats`"
$query = $this->select();
$query->from($this, array('cat_id', 'cat_name'));

Wir geben einfach der from()-Methode sowohl die aktuelle Tabelle (sprich: die Instanz des Models) als auch ein Array der zu holenden Spalten mit. Falls es nur eine Spalte ist, kann auch ein String übergeben werden.

Außerdem können wir die from()-Methode auch direkt an die select()-Methode anfügen (=Fluent Interface). Obiges Beispiel mit nur einer Spalte und Fluent Interface sieht so aus:

// "SELECT `cats`.`cat_name` FROM `cats`"
$query = $this->select()->from($this, 'cat_name');

Abfragen absenden

Was genau müssen wir eigentlich tun, wenn wir die Abfrage tatsächlich absenden wollen? Wir greifen auf die fetchAll()-Methode zurück und übergeben unsere Abfrage als Parameter.

$results = $this->fetchAll($query);

Der Rückgabewert, den wir in $results speichern, enthält ein Objekt vom Typ Zend_Db_Table_Rowset. Hört sich erstmal kompliziert an, ermöglicht uns aber objektorientiert auf die Ergebnisse zuzugreifen.

$query = $this->select()->from($this, 'cat_name');
$results = $this->fetchAll($query);
foreach ($results as $row) {
    echo $row->cat_name;
}

$row ist dabei jeweils vom Typ Zend_Db_Table_Row, daher der Zugriff über die Methode cat_name statt über einen Array-Schlüssel oder so. Das ist natürlich ziemlich cool. Ziemlich ZFig und ziemlich cool.

Möglich ist auch das Zurückgeben der ersten Zeile.

$firstRow = $this->fetchRow($query);

In diesem Fall ist $firstRow ebenfalls vom Typ Zend_Db_Table_Row und ermöglicht uns – ihr ahnt es – ebenfalls den objektorientierten Zugriff.

Ein spezieller Fall des SELECTs stellt die Methode find() dar. Dabei kann eine Zeile aus der Datenbank anhand des Primärschlüssels (i.d.R. also anhand der ID) geholt werden.

// "SELECT `cats`.* FROM `cats` WHERE `cats`.`cat_id` = 17"
$singleRow = $this->find(17);

Zurück kommt auch hier eine Zend_Db_Table_Row-Instanz.

Bedingungen mit where()

Natürlich wollen wir auch Bedingungen zu unseren Abfragen hinzufügen. Das geht hauptsächlich über die where()-Methode. Dabei übernimmt das ZF für uns auch das so wichtige Entschärfen von Benutzereingaben, so dass das Risiko für SQL-Injection vermindert wird. Damit dieses Feature aber auch genutzt werden kann, müssen wir Bedingungen wie folgt notieren:

// "SELECT `cats`.* FROM `cats` WHERE `cats`.`cat_name` = 'entschärfter String'"
$name = $_REQUEST['cat_name']; // kommt z.B. aus einem Formular
$query = $this->select()->where("cat_name = ?", $name);
$results = $this->fetchAll($query);

Das ganze funktioniert auch mit mehreren Bedingungen.

$query = $this->select()
    ->where("cat_name = ?", $name)
    ->where("cat_color = ?", $color);

Daten einfügen und aktualisieren

Das Hinzufügen neuer Daten geschieht recht easy über die insert()-Methode. Als Parameter müssen wir die Daten übergeben, die wir eingefügt haben wollen. Die erwartete Form ist hier ein Array mit Spaltennamen als Schlüssel und dem einzufügenden Wert als… well, Wert 😀

$data = array(
    'cat_name' => $name,
    'cat_color' => $color
);
$this->insert($data);

Ähnlich verhält es sich mit einem Update, nur dass wir hier noch eine Bedingung angeben müssen. Diese kann entweder als String oder – um das eingangs erwähnte Entschärfungs-Feature zu nutzen – als Array übergeben werden.

$data = array(
    'cat_name' => $name,
    'cat_color' => $color
);

// as string
$where = 'cat_id = 17';
// as array with escaped value
$where = array('cat_id = ?' => 17);

$this->update($data, $where);

Im Array können dann auch mehrere Bedingungen auf diese Art und Weise definiert werden.

$where = array(
    'cat_id = ?' => 17,
    'cat_age > ?' => 1
);

Alternativ können auch Daten geholt, bearbeitet und über save() persistent gespeichert werden.

$row = $this->find(17);
$row->color = "ginger";
$row->save();

DELETE

So langsam könnt ihr selbst erraten, wie das hier funktioniert, oder? 🙂 delete()-Methode anwenden, WHERE-Bedingung ähnlich wie bei update() übergeben und fertig.

$where = 'cat_color = "ginger"';
$this->delete($where);

LIMIT, ORDER, HAVING

Um eure Gehirnwindungen noch ein bisschen zu fordern, mal eine etwas heftigere Abfrage. Wir wollen alle Farben der Katzen nach Name absteigend sortiert haben. Außerdem wollen wir nur die relevanten Farben aufgezählt haben, also nur Farben, die bei mehr als 17 Katzen nachgewiesen werden können. Wir wollen aber auch nicht mehr als 3 Farben auf diese Art und Weise darstellen.

Die vorherigen Ausführungen und der kommentierte Quellcode sollten zum Verständnis reichen.

// "SELECT
//   `cats`.`cat_color` AS `color`,
//   COUNT(cat_color) AS `count`
// FROM `cats`
// GROUP BY cat_color
// HAVING count > 17
// ORDER BY `color` DESC
// LIMIT 3"
$query = $this->select()
    ->from($this, array(
        // alias => column-name
        'color' => 'cat_color',
        'count' => 'COUNT(cat_color)'
    ))
    ->order('color DESC')
    ->group('cat_color')
    ->having('count > 17')
    // first param: count, second: offset (optional)
    ->limit(3, 0);

Lazy Loading

Die verschiedenen Methoden können übrigens in beliebiger Reihenfolge aufgerufen werden. Einzig das $this->select() am Anfang ist wichtig. Durch dieses Lazy Loading genannte Feature ist es möglich auch nach dem eigentlichen Aufbereiten der Abfrage beispielsweise noch weitere WHERE-Bedingungen hinzuzufügen. Einmal mit gearbeitet werdet ihr es schätzen lernen.

Wrapping Up

Ok, reicht, oder? Wir haben zwar bisher nur Operationen in einer Tabelle behandelt, jedoch sollte das erstmal genug Stoff gewesen sein. Im nächsten Teil geht es dann weiter mit tabellenübergreifenden Abfragen. Dazu sei euch an dieser Stelle schon mal mein Artikel über JOINs ans Herz gelegt 😉

Falls noch Fragen offen sind, freue ich mich sie in den Kommentaren zu beantworten. Ggf. werden ich den Artikel nachbearbeiten und wichtige Infos einfügen, sollte ich welche vergessen haben.

Autor: Enno

Ich bin Enno. PHP ist mein Ding, aber auch alles Neue rund um die Themen HTML5, CSS3 & Co finde ich interessant. Ich mag es Leuten zu helfen und mein Wissen weiterzugeben. Sollte dir mein Beitrag gefallen haben, lass doch nen Kommentar da oder benutze einen der Social Buttons, um deinen Dank auszudrücken ;)