3.3. Zend_Db_Select

3.3.1. Introduction

Zend_Db_Select est un outil destiné vous aider à construire des instructions SQL SELECT de indépendemment du SGBD utilisé. Bien sûr cet outil ne peut pas être parfait, mais c'est quand même une grande avancée en matière de portabilité de vos requêtes. De plus, cet outil vous aide à rendre vos requêtes résistantes aux attaques du type "injection SQL".

Le moyen le plus facile de créer une instance de Zend_Db_Select est d'utiliser la méthode Zend_Db_Adapter::select().

<?php

require_once 'Zend/Db.php';

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

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

$select = $db->select();
// $select is now a Zend_Db_Select object configured for use only with the PDO_MYSQL adapter.
?>

Vous construisez ensuite une requête SELECT en utilisant cet objet et ses méthodes, puis vous générez une chaîne à passer comme requête ou comme argument d'une méthode fetch*() de Zend_Db_Adapter.

<?php

//
// SELECT *
//     FROM round_table
//     WHERE noble_title = "Sir"
//     ORDER BY first_name
//     LIMIT 10 OFFSET 20
//

// vous pouvez utiliser le style itératif
$select->from('round_table', '*');
$select->where('noble_title = ?', 'Sir');
$select->order('first_name');
$select->limit(10,20);

// ou le style fluide
$select->from('round_table', '*')
       ->where('noble_title = ?', 'Sir')
       ->order('first_name')
       ->limit(10,20);

// quoiqu'il en soit, le résultat est récupéré
$sql = $select->__toString();
$result = $db->fetchAll($sql);

// vous pouvez aussi passer directement l'objet $select;
// Zend_Db_Adapter est assez 'intelligent' pour appeler __toString() lorsqu'on
// lui passe des objets Zend_Db_Select : il obtient ainsi la requête
$resultat = $db->fetchAll($select);

?>

Vous pouvez aussi utiliser des marqueurs nommés dans vos requêtes, au lieu de l'échappement direct.

<?php

//
// SELECT *
//     FROM round_table
//     WHERE noble_title = "Sir"
//     ORDER BY first_name
//     LIMIT 10 OFFSET 20
//

$select->from('round_table', '*')
       ->where('noble_title = :title')
       ->order('first_name')
       ->limit(10,20);

// quoiqu'il en soit, le résultat est récupéré, cette
// fois-ci en spécifiant les données à  associer à  la requête
$params = array('title' => 'Sir');
$result = $db->fetchAll($select, $params);

?>

3.3.2. Sélectionner les colonnes d'une table : FROM

Pour sélectionner les colonnes d'une table particulière, utilisez la méthode from(), en spécifiant la table et les colonnes que vous désirez. Vous pouvez spécifier des alias pour les tables comme pour les colonnes et vous pouvez utiliser from() autant de fois que nécessaire.

<?php

// crée un objet $db, on suppose l'utilisation de MySQL
$select = $db->select();

// SELECT a, b, c FROM une_table
$select->from('une_table', 'a, b, c');
// pareil :
$select->from('une_table', array('a', 'b', 'c');

// SELECT barre.col FROM toto AS barre
$select->from('toto AS barre', 'barre.col');

// SELECT toto.col AS col1, barre.col AS col2 FROM toto, barre
$select->from('toto', 'toto.col AS col1');
$select->from('barre', 'barre.col AS col2');

?>

3.3.3. Sélectionner les colonnes de tables jointes : JOIN

Pour sélectionner des colonnes de tables jointes, utilisez la méthode join(). Spécifiez d'abord le nom de la table à joindre, puis la condition pour la jointure, et enfin les colonnes que vous voulez obtenir. Vous pouvez utiliser join() autant de fois que nécessaire.

<?php

// crée un objet $db, on suppose l'utilisation de MySQL
$select = $db->select();

//
// SELECT foo.*, bar.*
//     FROM foo
//     JOIN bar ON foo.id = bar.id
//
$select->from('foo', '*');
$select->join('bar', 'foo.id = bar.id', '*');

?>

Pour le moment, seule la syntaxe JOIN est prise en charge; les syntaxes LEFT JOIN, RIGHT JOIN etc, ne sont pas prises en charge. Les versions futures prendront en charge ces syntaxes de manière indépendante de la base de données.

3.3.4. Conditions WHERE

Pour ajouter des conditions WHERE, utilisez la méthode where(). Vous pouvez lui passer une chaîne classique ou une chaîne contenant un emplacement réservé indiqué par un point d'interrogation et la valeur à échapper à la place de l'emplacement (celle-ci sera échappée à l'aide de la méthode Zend_Db_Adapter::quoteInto()).

Appeler plusieurs fois where() aura pour effet d'enchaîner les conditions avec le mot-clé AND; si vous désirez utiliser le mot-clé OR, utilisez la méthode orWhere().

<?php

// crée un objet $db, on suppose l'utilisation de MySQL
$select = $db->select();

//
// SELECT *
//     FROM round_table
//     WHERE noble_title = "Sir"
//     AND favorite_color = "yellow"
//
$select->from('round_table', '*');
$select->where('noble_title = "Sir"'); valeur embarquée
$select->where('favorite_color = ?', 'yellow'); // valeur échappée

//
// SELECT *
//     FROM foo
//     WHERE bar = "baz"
//     OR id IN("1", "2", "3")
//
$select->from('foo', '*');
$select->where('bar = ?', 'baz');
$select->orWhere('id IN(?)', array(1, 2, 3));

?>

3.3.5. Clause GROUP BY

Pour regrouper les lignes, utilisez la méthode group() autant de fois que vous le souhaitez.

<?php

// crée a objet $db, puis obtention de l'outil SELECT
$select = $db->select();

//
// SELECT COUNT(id)
//     FROM foo
//     GROUP BY bar, baz
//
$select->from('foo', 'COUNT(id)');
$select->group('bar');
$select->group('baz');

// un appel équivalent :
$select->group('barre, baz');

// un autre appel équivalent :
$select->group(array('barre', 'baz'));

?>

3.3.6. Conditions HAVING

Pour ajouter des conditions HAVING à la requête SELECT, utilisez la méthode having(). Cette méthode est identique au niveau du fonctionnement à la méthode where().

Si vous appelez plusieurs fois having(), les conditions sont enchaînées avec le mot-clé AND; si vous désirez utiliser le mot-clé OR, utilisez la méthode orHaving().

<?php

// crée a objet $db, puis obtention de l'outil SELECT
$select = $db->select();

//
// SELECT COUNT(id) AS count_id
//     FROM foo
//     GROUP BY bar, baz
//     HAVING count_id > "1"
//
$select->from('foo', 'COUNT(id) AS count_id');
$select->group('bar, baz');
$select->having('count_id > ?', 1);

?>

3.3.7. Clause ORDER BY

Pour organiser les colonnes, utilisez la méthode order() autant de fois que vous le souhaitez.

<?php

// crée a objet $db, puis obtention de l'outil SELECT
$select = $db->select();

//
// SELECT * FROM round_table
//     ORDER BY noble_title DESC, first_name ASC
//
$select->from('round_table', '*');
$select->order('noble_title DESC');
$select->order('first_name');

// un appel équivalent :
$select->order('titre DESC, prenom');

// un autre appel équivalent :
$select->order(array('titre DESC', 'prenom'));

?>

3.3.8. Limiter le résultat par un décompte et un offset

Zend_Db_Select offre une abstraction de la clause LIMIT. Pour de nombreuses bases de données, telles que MySQL et PostgreSQL, ceci est relativement aisé dans la mesure où ces bases de données prennent en charge la syntaxe "LIMIT :decompte [OFFSET :offset]".

Pour d'autres bases de données, telles que Microsoft SQL Server et Oracle, ce n'est pas si facile car elles ne prennent pas du tout en charge les clauses LIMIT. MS-SQL ne possède qu'une clause TOP et, pour Oracle, la requête doit être écrite d'une manière spéciale afin d'émuler une clause LIMIT. Grâce à la manière dont fonctionne en interne Zend_Db_Select, nous pouvons réécrire directement la requête SELECT afin d'émuler la fonctionnalité LIMIT des SGBD Open Source cités ci-dessus.

Pour limiter le résultat d'une requête par un décompte et un offset, utilisez la méthode limit() en lui passant un décompte et, facultativement, un offset.

<?php

// d'abord, une clause simple "LIMIT :decompte"
$select = $db->select();
$select->from('toto', '*');
$select->order('id');
$select->limit(10);

//
// En MySQL/PostgreSQL/SQLite, cette instruction se traduit par :
//
// SELECT * FROM toto
//     ORDER BY id ASC
//     LIMIT 10
//
// Mais en Microsoft SQL, cela se traduit par :
//
// SELECT TOP 10 * FROM TOTO
//     ORDER BY id ASC
//
//

// maintenant, une clause plus complexe "LIMIT :decompte OFFSET :offset"
$select = $db->select();
$select->from('toto', '*');
$select->order('id');
$select->limit(10, 20);

//
// En MySQL/PostgreSQL/SQLite, cette instruction se traduit par :
//
// SELECT * FROM toto
//     ORDER BY id ASC
//     LIMIT 10 OFFSET 20
//
// Mais en Microsoft SQL, qui ne prend pas en charge les offset, cela se traduit par
// quelque chose du style :
//
// SELECT * FROM (
//     SELECT TOP 10 * FROM (
//         SELECT TOP 30 * FROM toto ORDER BY id DESC
//     ) ORDER BY id ASC
// )
//
// Zend_Db_Adapter se charge automatiquement pour vous de la traduction de la requête.
//


?>

3.3.9. Limiter le résultat par pages

Zend_Db_Select peut aussi limiter les résultats par pages. Si vous souhaitez obtenir une "page" particulière du résultat, utilisez la méthode limitPage(); passez-lui d'abord le numéro de la page que vous voulez et ensuite le nombre de lignes qui apparaissent sur chaque page.

<?php

// construction de la requête SELECT de base...
$select = $db->select();
$select->from('toto', '*');
$select->order('id');

// ... et on limite le résultat à la page 3 où chaque page contient 10 lignes
$select->limitPage(3, 10);

//
// En MySQL/PostgreSQL/SQLite, cette instruction se traduit par :
//
// SELECT * FROM toto
//     ORDER BY id ASC
//     LIMIT 10 OFFSET 20
//

?>