Kapitel 9. Zend_Db

Inhaltsverzeichnis

9.1. Zend_Db_Adapter
9.1.1. Einführung
9.1.2. Quoting gegen SQL-Injection
9.1.3. Direkte Abfragen
9.1.4. Transaktionen
9.1.5. Zeilen einfügen
9.1.6. Zeilen aktualisieren
9.1.7. Zeilen löschen
9.1.8. Zeilen abrufen
9.2. Zend_Db_Profiler
9.2.1. Einführung
9.2.2. Den Profiler verwenden
9.2.3. Fortgeschrittene Profiler Verwendung
9.3. Zend_Db_Select
9.3.1. Overview of the Select Object
9.3.2. Creating a Select Object
9.3.3. Building Select queries
9.3.4. Executing Select Queries
9.3.5. Other methods
9.4. Zend_Db_Table
9.4.1. Introduction to Table Class
9.4.2. Defining a Table Class
9.4.3. Creating an Instance of a Table
9.4.4. Inserting Rows to a Table
9.4.5. Updating Rows in a Table
9.4.6. Deleting Rows from a Table
9.4.7. Finding Rows by Primary Key
9.4.8. Querying for a Set of Rows
9.4.9. Querying for a Single Row
9.4.10. Retrieving Table Metadata Information
9.4.11. Caching Table Metadata
9.4.12. Customizing and Extending a Table Class
9.5. Zend_Db_Table_Row
9.5.1. Introduction
9.5.2. Fetching a Row
9.5.3. Writing rows to the database
9.5.4. Serializing and unserializing rows
9.5.5. Extending the Row class
9.6. Zend_Db_Table_Rowset
9.6.1. Introduction
9.6.2. Fetching a Rowset
9.6.3. Retrieving Rows from a Rowset
9.6.4. Retrieving a Rowset as an Array
9.6.5. Serializing and Unserializing a Rowset
9.6.6. Extending the Rowset class
9.7. Zend_Db_Table Relationships
9.7.1. Introduction
9.7.2. Defining Relationships
9.7.3. Fetching a Dependent Rowset
9.7.4. Fetching a Parent Row
9.7.5. Fetching a Rowset via a Many-to-many Relationship
9.7.6. Cascading Write Operations

9.1. Zend_Db_Adapter

9.1.1. Einführung

Zend_Db_Adapter ist die Datenbankabstraktionsschicht des Zend-Frameworks. Sie basiert auf PDO und erlaubt es, über eine einheitliche API (Schnittstelle) Verbindungen zu allen von PDO unterstützten Datenbanksystemen aufzubauen und mit diesen zu arbeiten. Unterstützte Datenbanken sind unter anderem: Microsoft SQL Server, MySQL, PostgreSQL und SQLite.

Um eine Instanz von Zend_Db_Adapter für ein spezielles Datenbank-Backend zu erstellen muss man Zend_Db::factory() mit dem Namen des Adapters und einem Array von Parametern aufrufen, welche die Verbindung beschreiben. Hier ein Beispiel für einen Verbindungsaufbau zu einer MySQL-Datenbank namens "camelot" auf einem lokalen Server unter Verwendung des Benutzernamens "malory":

<?php

require_once 'Zend/Db.php';

$params = array ('host'     => '127.0.0.1',
                 'username' => 'malory',
                 'password' => '******',
                 'dbname'   => 'camelot');

$db = Zend_Db::factory('PDO_MYSQL', $params);

?>

Ähnlich erfolgt der Verbindungsaufbau zu einer SQlite-Datenbank namens "camelot.sq3":

<?php

require_once 'Zend/Db.php';

$params = array ('dbname' => 'camelot.sq3');

$db = Zend_Db::factory('PDO_SQLITE', $params);

?>

Ähnlich erfolgt der Verbindungsaufbau zu einer SQLite2-Datenbank namens "camelot.sq2":

<?php

require_once 'Zend/Db.php';

$params = array ('dbname' => 'camelot.sq2',
                 'dsnprefix' => 'sqlite2');

$db = Zend_Db::factory('PDO_SQLITE', $params);

?>

Wenn eine im RAM liegende SQlite-Datenbank benutzt werden soll wird kein DSN-Präfix angegeben und der Datenbankname lautet ":memory:".

So oder so kann immer die gleiche API verwendet werden um Abfragen auf die Datenbank durchzuführen.

9.1.2. Quoting[1] gegen SQL-Injection

Das so genannte Quoting sollte auf alle Werte angewendet werden, die in SQL-Anweisungen eingesetzt werden; dies hilft dabei, Angriffen per SQL-Injection vorzubeugen. Zend_Db_Adapter stellt (über das zugrunde liegende PDO Objekt) zwei hilfreiche Methoden für diesen Zweck bereit.

Die erste davon ist die quote()-Methode. Sie nimmt die für den jeweils verwendeten Datenbank-Adapter nötigen Umwandlungen vor; der Methode kann auch ein Array übergeben werden, dann gibt sie einen durch Kommata getrennten String mit den Array-Werten zurück, wobei jeder Wert entsprechend umgeschrieben wird (dies ist hilfreich für Funktionen, die einen Listen-Parameter erwarten).

<?php

// Erstelle ein $db-Objekt (am Beispiel eines Mysql-Adapters)

// Benutzung von quote() für einen einzelnen Wert
$value = $db->quote('St John"s Wort');
// $value ist nun '"St John\"s Wort"' (beachten Sie die umschließenden Anführungszeichen)

// Benutzung von quote() für ein Array
$value = $db->quote(array('a', 'b', 'c');
// $value ist nun '"a", "b", "c"' (ein durch Kommata getrennter String)

?>

Die Zweite ist die quoteInto()-Methode. Sie erwarten einen String mit einem Fragezeichen als Platzhalter und entweder einen einzelnen Wert oder ein Array von Werten, der/die an dieser Stelle eingefügt werden soll/en. Dies ist nützlich um Abfragen oder Bedingungen auf die Schnelle zu generieren. Die Behandlung der Werte ist dabei identisch wie bei der Nutzung der quote()-Methode.

<?php

// Erstelle ein $db-Objekt (am Beispiel eines Mysql-Adapters)

// setze einen einzelnen Wert in eine WHERE-Bedingung ein
$where = $db->quoteInto('id = ?', 1);
// $where ist nun 'id = "1"' (beachte die umschließenden Anführungszeichen)

// setze die Inhalte eines Arrays in eine WHERE-Bedingung ein
$where = $db->quoteInto('id IN(?)', array(1, 2, 3));
// $where ist nun 'id IN("1", "2", "3")' (ein durch Kommata getrennter String)

?>

9.1.3. Direkte Abfragen

Nachdem man eine Instanz von Zend_Db_Adapter hat, kann man direkt SQL-Abfragen ausführen. Zend_Db_Adapter übergibt diese Abfragen an das zugrunde liegende PDO-Objekt, welches sie aufbereitet und ausführt, und gibt ein PDOStatement-Objekt zurück, damit man die Ergebnisse (soweit vorhanden) weiter verarbeiten kann.

<?php

// Erstelle ein $db-Objekt und stelle dann eine 
// sicher formatierte SQL-Abfrage an die Datenbank.
$sql = $db->quoteInto(
    'SELECT * FROM example WHERE date > ?',
    '2006-01-01'
);
$result = $db->query($sql);

// verwende das PDOStatement $result, um alle Zeilen als Array abzurufen
$rows = $result->fetchAll();

?>

Man kann Daten automatisch in eine Abfrage einbinden. Das bedeutet, dass man mehrere benannte Platzhalter in der Abfrage unterbringen und dann ein Array mit den Daten übergeben kann, welche diese Platzhalter ersetzen. Die ersetzten Werte werden dabei automatisch umgeschrieben, um größere Sicherheit gegen Angriffe per SQL-Injection zu bieten.

<?php

// Erstelle ein $db-Objekt und frage dann die Datenbank ab.
// Dieses Mal: Verwende Platzhalter.
$result = $db->query(
    'SELECT * FROM example WHERE date > :placeholder',
    array('placeholder' => '2006-01-01')
);

// verwende das PDOStatement $result, um alle Zeilen als Array abzurufen
$rows = $result->fetchAll();

?>

Optional ist es auch möglich, Daten manuell aufzubereiten und mit der SQL-Abfrage zu verbinden. Hierfür verwendet man die prepare()-Methode, um ein vorbereitetes PDOStatement zu erhalten, welches man direkt bearbeiten kann.

<?php

// Erstelle ein $db-Objekt und frage dann die Datenbank ab.
// Dieses Mal: Bereite ein PDOStatement für das manuelle Verbinden auf.
$stmt = $db->prepare('SELECT * FROM example WHERE date > :placeholder');
$stmt->bindValue('placeholder', '2006-01-01');
$stmt->execute();

// verwende das PDOStatement $result, um alle Zeilen als Array abzurufen
$rows = $stmt->fetchAll();

?>

9.1.4. Transaktionen

Standardmäßig sind PDO (und damit auch Zend_Db_Adapter) im "auto-commit" Modus. Das heißt, dass alle Abfragen sofort unwiderruflich ausgeführt werden, wenn sie gestellt werden. Wenn sie innerhalb einer Transaktion ausgeführt werden sollen, kann einfach die Methode beginTransaction() aufgerufen werden, dann besteht die Möglichkeit, Änderungen entweder mit commit() zu bestätigen oder mit rollBack() rückgängig zu machen. Zend_Db_Adapter kehrt in den "auto-commit" Modus zurück, bis beginTransaction() wieder aufgerufen wird.

<?php

// Erstelle ein $db-Objekt und starte dann eine Transaktion
$db->beginTransaction();

// Versuche eine Abfrage
// wenn erfolgreich, verarbeite die Änderungen
// wenn nicht erfolgreich, setze Änderungen zurück
try {
    $db->query(...);
    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    echo $e->getMessage();
}

?>

9.1.5. Zeilen einfügen

Der Einfachheit halber kann man die insert()-Methode verwenden, um eine INSERT Anweisung zu erstellen und mit den einzufügenden Daten zu verknüpfen. (Die verknüpften Daten werden automatisch umgeschrieben, um Angriffe per SQL-Injection zu vermeiden.)

Der Rückgabewert ist nicht die zuletzt eingefügte ID, da eine Tabelle keine automatisch inkrementierte Spalte haben muss. Stattdessen enthält der Rückgabewert die Zahl der beeinflussten Zeilen (normalerweise 1). Um die ID des zuletzt eingefügten Datensatzes zu ermitteln, kann man die lastInsertId()-Methode nach dem Einfügen aufrufen.

<?php

//
// INSERT INTO round_table
//     (noble_title, first_name, favorite_color)
//     VALUES ("King", "Arthur", "blue");
//

// Erstelle ein $db-Objekt und dann...
// die einzufügenden Daten im Spalte => Wert Format
$row = array (
    'noble_title'    => 'King',
    'first_name'     => 'Arthur',
    'favorite_color' => 'blue',
);

// die Tabelle, in der die Daten eingefügt werden sollen
$table = 'round_table';

// füge die Daten ein und hole die ID der eingefügten Zeile
$rows_affected = $db->insert($table, $row);
$last_insert_id = $db->lastInsertId();

?>

9.1.6. Zeilen aktualisieren

Der Einfachheit halber kann man die update()-Methode verwenden, um eine UPDATE-Anweisung zu erstellen und mit den zu aktualisierenden Daten zu verknüpfen. (Die verknüpften Daten werden automatisch umgeschrieben, um Angriffe per SQL-Injection zu vermeiden.)

Es ist möglich, eine WHERE-Klausel zu übergeben um festzulegen, welche Zeilen aktualisiert werden sollen.

<?php

//
// UPDATE round_table
//     SET favorite_color = "yellow"
//     WHERE first_name = "Robin";
//

// Erstelle ein $db-Objekt und dann...
// die neuen Werte, die aktualisiert werden sollen, im Spalte => Wert Format.
$set = array (
    'favorite_color' => 'yellow',
);

// die zu aktualisierende Tabelle
$table = 'round_table';

// die WHERE-Klausel
$where = $db->quoteInto('first_name = ?', 'Robin');

// aktualisiere die Tabelle und hole die Anzahl der beeinflussten Zeilen
$rows_affected = $db->update($table, $set, $where);

?>

If you have multiple WHERE clauses that should be applied to the UPDATE query, you can provide an array of such clauses to the update() method. These clauses are combined with the AND operator. Combining clauses with the OR operator must be performed manually:

<?php
$set = array('favorite_color' => 'yellow');

// multiple conditions combined with AND
$where   = array(
    $db->quoteInto('first_name = ?', $firstName),
    $db->quoteInto('noble_title = ?', $nobleTitle)
    );
$count   = $db->update('round_table', $set, $where);

// multiple conditions combined with OR
$firstNameOne = $db->quote($firstNameOne);
$firstNameTwo = $db->quote($firstNameTwo);
$where        = "first_name = $firstNameOne OR first_name = $firstNameTwo";
$count        = $db->update('round_table', $set, $where);

?>
[Anmerkung] Anmerkung

The values and identifiers of the WHERE clause(s) are not quoted for you. If you have values or identifiers that require quoting, you are responsible for doing this. Use the quote(), quoteInto(), and quoteIdentifier() methods of the database adapter.

9.1.7. Zeilen löschen

Der Einfachheit halber kann man die delete()-Methode verwenden, um eine DELETE Anweisung zu erstellen. Es ist möglich, eine WHERE-Klausel zu übergeben um anzugeben, welche Zeilen gelöscht werden sollen. (Man beachte, dass die WHERE-Klausel kein verknüpfter Parameter ist, so dass die enthaltenen Werte manuell aufbereitet werden müssen, um Angriffe per SQL-Injection zu vermeiden.)

<?php

//
// DELETE FROM round_table
//     WHERE first_name = "Patsy";
//

// Erstelle ein $db-Objekt und dann...
// die Tabelle, aus der gelöscht werden soll
$table = 'round_table';

// die WHERE-Klausel
$where = $db->quoteInto('first_name = ?', 'Patsy');

// aktualisiere die Tabelle und hole die Anzahl der beeinflussten Zeilen
$rows_affected = $db->delete($table, $where);

?>

As with the update() method, you may use an array of WHERE clauses for the $where argument to the delete() method.

[Anmerkung] Anmerkung

The values and identifiers of the WHERE clause(s) are not quoted for you. If you have values or identifiers that require quoting, you are responsible for doing this. Use the quote(), quoteInto(), and quoteIdentifier() methods of the database adapter.

9.1.8. Zeilen abrufen

Obwohl man die Datenbank direkt mit der query()-Methode abfragen kann, will man meistens nur Zeilen selektieren und die Ergebnisse zurückerhalten. Diese Aufgabe erledigen die fetch*()-Methoden. Für jede der fetch*()-Methoden übergibt man eine SQL-SELECT-Anweisung; wenn man benannte Platzhalter in der Anweisung verwendet, kann man zudem ein Array mit zu verknüpfenden Werten übergeben, die dann zum Schutz gegen SQL-Injection aufbereitet und in die Anweisung eingesetzt werden. Diese fetch*()-Methoden gibt es:

  • fetchAll()

  • fetchAssoc()

  • fetchCol()

  • fetchOne()

  • fetchPairs()

  • fetchRow()

<?php

// Erstelle ein $db-Objekt und dann...

// hole alle Spalten aller Zeilen als sequentielles Array
$result = $db->fetchAll(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// hole alle Spalten aller Zeilen als assoziatives Array
// die erste Spalte wird als Array-Schlüssel verwendet
$result = $db->fetchAssoc(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// hole die erste Spalte aller zurück gelieferten Zeilen
$result = $db->fetchCol(
    "SELECT first_name FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// hole nur den ersten Wert
$result = $db->fetchOne(
    "SELECT COUNT(*) FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// hole eine Sequenz von Schlüssel-Wert-Paaren; die erste Spalte ist 
// der Array-Schlüssel, die zweite Spalte ist der Array-Wert
$result = $db->fetchPairs(
    "SELECT first_name, favorite_color FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// hole nur die erste der zurück gelieferten Zeilen
$result = $db->fetchRow(
    "SELECT * FROM round_table WHERE first_name = :name",
    array('name' => 'Lancelot')
);

?>


[1] Der englische Begriff "quoting" bezeichnet das Entfernen einer Sonderbedeutung eines Zeichens durch Voranstellen oder Umklammern mit speziellen Zeichen (Backslash, Anführungsstriche usw.) Die genauen Regeln für das jeweilige Quoting sind abhängig vom Zielsystem, an welches die Daten übergeben werden.