Chapitre 9. Zend_Db

Table des matières

9.1. Zend_Db_Adapter
9.1.1. Introduction
9.1.2. Se protéger des injections SQL
9.1.3. Requêtes Directes
9.1.4. Transactions
9.1.5. Insertion de Lignes
9.1.6. Mise à jour de lignes
9.1.7. Suppression de Lignes
9.1.8. Récupération de lignes
9.2. Zend_Db_Profiler
9.2.1. Introduction
9.2.2. Utiliser le profileur
9.2.3. Utilisation avancée du profileur
9.3. Zend_Db_Select
9.3.1. Vue d'ensemble de l'objet Select
9.3.2. Créer un objet Select
9.3.3. Construction de requêtes Select
9.3.4. Exécuter des requêtes Select
9.3.5. Autres méthodes
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. Introduction

Zend_Db_Adapter est l'API de la couche d'abstraction de base de données pour le Zend Framework. Basé sur PDO, vous pouvez utiliser Zend_Db_Adapter pour vous connecter sur tout les SGBD supportés, en utilisant la même API. Les SGBD supportés sont entre autres : Microsoft SQL Server, MySQL, PostgreSQL, SQLite etc.

Pour créer une instance de Zend_Db_Adapter pour vous connecter à votre base de données, vous devez appeler Zend_Db::factory() avec le nom de l'adapteur et un tableau de paramètres décrivant la connexion. Par exemple, pour vous connecter à une base de données MySQL appelée "camelot" sur votre machine locale et avec le nom d'utilisateur "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);
?>

De la même manière, pour vous connecter à une base de données SQLite appelée "camelot.sq3" :

<?php
require_once 'Zend/Db.php';

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

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

De la même manière, pour vous connecter à une base de données SQLite2 appelée "camelot.sq2" (pour une base en mémoire, ne spécifiez pas de dsnprefix et affectez ":memory:" à dbname) :

<?php
require_once 'Zend/Db.php';

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

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

Quelque soit le SGBD, vous aurez la possibilité d'utiliser exactement la même API pour interroger la base de données.

9.1.2. Se protéger des injections SQL

Vous devriez toujours échapper les valeurs destinées à être utilisées dans une requête SQL ; cela permet de se prémunir des injections SQL. Zend_Db_Adapter fournies deux méthodes (via l'objet PDO) pour vous aider à échapper les valeurs.

La première est la méthode quote(). Elle va échapper une valeur scalaire conformément aux spécificités de la base de données. Si vous essayer d'échapper un tableau, la méthode va retourner une chaîne contenant chaque valeur du tableau, séparée par des virgules, chacune correctement protégée (c'est pratique dans le cas de fonctions qui prennent un paramètre de type liste).

<?php
// crée un objet $db, on suppose l'utilisation de MySQL

// échappement d'un scalaire
$value = $db->quote('St John"s Wort');
// $value is now '"St John\"s Wort"' (note the surrounding quotes)

// échappement d'un tableau
$value = $db->quote(array('a', 'b', 'c');
// $value vaut maintenant '"a", "b", "c"' (une chaîne séparée par des virgules)
?>

La seconde est la méthode quoteInto(). Vous fournissez une requête de base, avec des points d'interrogations comme caractère à remplacer, et un scalaire ou un tableau à protéger. C'est pratique pour construire rapidement des requêtes et des clauses. Les scalaires et les tableaux sont traitées de la même façon que dans la méthode quote().

<?php
// crée un objet $db, on suppose l'utilisation de MySQL

// protection d'un scalaire dans une clause WHERE
$where = $db->quoteInto('id = ?', 1);
// $where vaut maintenant 'id = "1"' (notez bien les guillemets supplémentaires)

// protection d'un tableau dans la clause WHERE
$where = $db->quoteInto('id IN(?)', array(1, 2, 3));
// $where vaut maintenant 'id IN("1", "2", "3")' (une chaîne séparée par des virgules)
?>

9.1.3. Requêtes Directes

Une fois que vous avez une instance de Zend_Db_Adapter, vous pouvez exécuter directement des requêtes SQL. Zend_Db_Adapter passe cette requête dans l'objet PDO sous-jacent, qui les prépare et les exécute, et vous retourne ensuite une objet PDOStatement pour que vous puissez manipuler les résultats, s'il y en a.

<?php
// crée un objet $db et exécute ensuite la requête
// avec une requête SQL correctement protégée
$sql = $db->quoteInto(
    'SELECT * FROM example WHERE date > ?',
    '2006-01-01'
);
$result = $db->query($sql);

// utilisez l'objet de type PDOStatement $result pour récupérer toutes les lignes de résultat dans un tableau
$rows = $result->fetchAll();
?>

Vous pouvez associer automatiquement vos données à la requête. Cela signifie que vous pouvez définir de multiples marqueurs de remplacement réservés et nommés dans la requête, et ensuite passer un tableau de données qui vont se substituer à ces marqueurs. Les valeurs de remplacement sont automatiquement protégées, pour prévenir les attaques par injection SQL.

<?php
// crée un objet $db, et interroge la base de données.
// cette fois, utilisez un marqueur de remplacement.
$result = $db->query(
    'SELECT * FROM example WHERE date > :placeholder',
    array('placeholder' => '2006-01-01')
);

// utiliser le PDOStatement $result pour récupérer toutes les lignes de résultat dans un tableau
$rows = $result->fetchAll();
?>

Optionellement, vous pouvez préparer et lier manuellement une instruction SQL. Pour ce faire, utilisez la méthode prepare() pour obtenir un objet de type PDOStatement, que vous pouvez manipuler directement.

<?php
// créé un objet $db, et interroge la base de données.
// cette fois-ci, utilisez un objet PDOStatement pour une liaison manuelle.
$stmt = $db->prepare('SELECT * FROM example WHERE date > :placeholder');
$stmt->bindValue('placeholder', '2006-01-01');
$stmt->execute();

// utiliser le PDOStatement $result pour récupérer toutes les lignes de résultat dans un tableau
$rows = $stmt->fetchAll();
?>

9.1.4. Transactions

Par défaut, PDO (and ainsi Zend_Db_Adapter) sont en mode "auto-commit". Cela signifie que chaque requête est validée à chaque exécution. Si vous souhaitez les exécuter à l'intérieur d'une transaction, appelez simplement la méthode beginTransaction() vous pourrez alors valider (commit()) ou annuler (rollBack()) vos changements. Zend_Db_Adapter retourne en mode "auto-commit" jusqu'à ce que vous appeliez de nouveau beginTransaction().

<?php
// créé une objet $db, puis débute une transaction.
$db->beginTransaction();

// tente une requête
// si elle réussie, validez les changements avec commit()
// si elle échoue, annuler les changements avec rollback()
try {
    $db->query(...);
    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    echo $e->getMessage();
}
?>

9.1.5. Insertion de Lignes

Vous avez la possibilité d'utiliser la méthode insert() pour créer une instruction INSERT, et lui lier les données pour l'insertion. (Les données liées sont automatiquement protégées pour prévenir tout risque d'injection SQL).

La valeur retournée n'est pas le dernier identifiant d'insertion, la table ne contenant peut-être pas de colonne auto-incrémentée ; à la place, la valeur retournée est le nombre de lignes affectées (habituellement 1). Si vous souhaitez l'ID du dernier identifiant généré, appelez la méthode lastInsertId() après l'insertion.

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

// créé un objet $db et ensuite...
// les données des colonnes à insérer en utilisant la syntaxe nom_colonne => valeur_colonne
$row = array (
    'noble_title'    => 'King',
    'first_name'     => 'Arthur',
    'favorite_color' => 'blue',
);

// la table dans laquelle la ligne doit être insérée
$table = 'round_table';

// insérez la ligne, et récupérez son identifiant
$rows_affected = $db->insert($table, $row);
$last_insert_id = $db->lastInsertId();
?>

9.1.6. Mise à jour de lignes

Vous avez la possibilité d'utiliser la méthode update() pour créer une instruction UPDATE et lier vos données pour la mise à jour. (Les données liées sont automatiquement protégées pour prévenir tout risque d'injection SQL).

Vous pouvez fournir une clause WHERE facultative pour spécifier quelle ligne mettre à jour.

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

// crée un objet $db, en ensuite...
// les nouvelles valeurs à mettre à jour, en utilisant la syntaxe nom_colonne => valeur_colonne
$set = array (
    'favorite_color' => 'yellow',
);

// la table à mettre à jour
$table = 'round_table';

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

// mise à jour de la table, et récupération du nombre de lignes affectées
$rows_affected = $db->update($table, $set, $where);
?>

Si vous avez des clauses WHERE multiples qui doivent être appliquées à la requête UPDATE, vous pouvez fournir un tableau des clauses à la méthode update(). Ces clauses sont combinées avec l'opérateur AND. L'union de clauses avec l'opérateur OR doit être exécutée manuellement :

<?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);
?>
[Note] Note

Les valeurs et les identificateurs de la clause WHERE ne sont pas échappés pour vous. Si vous avez des valeurs ou les identificateurs qui nécessitent d'être échappés, vous êtes responsables de cette protection. Utilisez les méthodes de l'adaptateur de base de données : quote(), quoteInto(), et quoteIdentifier().

9.1.7. Suppression de Lignes

Vous avez la possibilité d'utiliser la méthode delete() pour créer une instruction DELETE. Vous pouvez fournir optionellement une clause WHERE. (Notez que la clause WHERE n'est pas un paramètre échappé, vous devez donc protéger vous-même sa valeur).

<?php
//
// DELETE FROM round_table
//     WHERE first_name = "Patsy";
//

// crée un objet $db, en ensuite...
// la table dont on veut supprimer des données
$table = 'round_table';

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

// mise à jour de la table, et récupération du nombre de lignes affectées
$rows_affected = $db->delete($table, $where);
?>

Comme pour la méthode update(), vous pouvez fournir un tableau des clauses WHERE en tant qu'argument $where de la méthode delete().

[Note] Note

Les valeurs et les identificateurs de la clause WHERE ne sont pas échappés pour vous. Si vous avez des valeurs ou les identificateurs qui nécessitent d'être échappés, vous êtes responsables de cette protection. Utilisez les méthodes de l'adaptateur de base de données : quote(), quoteInto(), et quoteIdentifier().

9.1.8. Récupération de lignes

Bien que vous puissiez interroger la base de données directement avec la méthode query() tout ce que vous avez besoin de faire et des sélectionner des lignes et récupérer le résultat. La série de méthodes fetch*() vous permet de le faire. Pour chaque méthode fetch*(), vous passez une instruction SQL; si vous utilisez des marqueurs de remplacement dans cette instruction, vous devrez aussi passer un tableau contenant la valeur à échapper, pour qu'elle soient protégées et remplacées dans la requête. Les méthodes fetch*() sont :

  • fetchAll()

  • fetchAssoc()

  • fetchCol()

  • fetchOne()

  • fetchPairs()

  • fetchRow()

<?php
// crée un objet $db, en ensuite...

// ?? retrouve les colonnes de chaque ligne comme un tableau séquentiel ??
$result = $db->fetchAll(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// ?? retrouve les colonnes de chaque ligne comme un tableau associatif ?? 
// la première colonne est utilisée comme la clé du tableau
$result = $db->fetchAssoc(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// retrouve la première colonne de résultat
$result = $db->fetchCol(
    "SELECT first_name FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// retrouve uniquement la première valeur
$result = $db->fetchOne(
    "SELECT COUNT(*) FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// retrouve la série de paries clé/valeurs; la première colonne et le tableau de clé
// la seconde le tableau de valeurs 
$result = $db->fetchPairs(
    "SELECT first_name, favorite_color FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// retrouve uniquement la première ligne retournée
$result = $db->fetchRow(
    "SELECT * FROM round_table WHERE first_name = :name",
    array('name' => 'Lancelot')
);
?>