Fervent utilisateur de Symfony, j'utilise donc l'ORM Propel.
Avant j'étais assez fan de ezPdo mais c'est toujours un ORM.
Pour préciser, l'ORM se place encore au-dessus de la couche d'abstraction et permet de manipuler son modèle comme des objets sans se soucier des requêtes qui peuvent être réalisées derrière. Le danger c'est de se retrouver sans s'en rendre compte avec des pages présentant un nombre de requêtes absolument énorme (c'est particulièrement vrai et difficile à corriger avec ezPdo), mais dans le cas de Propel ça s'optimise très bien. Par exemple sur le forum de mon jeu je tourne à 8 requêtes sur la page d'affichage d'un thread (la plus gourmande). Sans ORM je pourrais probablement réduire à 6 ou 5, mais pour 25% de réduction du nombre de requêtes je perdrais bien plus en beauté du code
Sinon pour la simple abstraction SQL je conseille toujours PEAR::ADODB2 qui est très complet.
Propel repose sur Creole qui est parfois un peu lourdingue dans sa syntaxe.
Et sinon dans le contexte de mon boulot j'ai parfois des collègues "pur php4" qui font du site jetable en série, et qui n'ont vraiment pas le temps de se préoccuper de ça. Je leur fournis une toute petite classe qui fait tout le travail dont tu parles et même un peu plus en quelques lignes, sa simplicité les a séduit. Je vous la fournis reformatée PHP5 :
Avant j'étais assez fan de ezPdo mais c'est toujours un ORM.
Pour préciser, l'ORM se place encore au-dessus de la couche d'abstraction et permet de manipuler son modèle comme des objets sans se soucier des requêtes qui peuvent être réalisées derrière. Le danger c'est de se retrouver sans s'en rendre compte avec des pages présentant un nombre de requêtes absolument énorme (c'est particulièrement vrai et difficile à corriger avec ezPdo), mais dans le cas de Propel ça s'optimise très bien. Par exemple sur le forum de mon jeu je tourne à 8 requêtes sur la page d'affichage d'un thread (la plus gourmande). Sans ORM je pourrais probablement réduire à 6 ou 5, mais pour 25% de réduction du nombre de requêtes je perdrais bien plus en beauté du code
Sinon pour la simple abstraction SQL je conseille toujours PEAR::ADODB2 qui est très complet.
Propel repose sur Creole qui est parfois un peu lourdingue dans sa syntaxe.
Et sinon dans le contexte de mon boulot j'ai parfois des collègues "pur php4" qui font du site jetable en série, et qui n'ont vraiment pas le temps de se préoccuper de ça. Je leur fournis une toute petite classe qui fait tout le travail dont tu parles et même un peu plus en quelques lignes, sa simplicité les a séduit. Je vous la fournis reformatée PHP5 :
Code PHP :
<?php
class SQL
{
const PLACEHOLDER_MARKER = ':'
protected static $queries;
protected static $connection;
/**
* Connexion à la base de données
*
* @todo DB-Abstraction (mysql_connect)
*/
public static function connect($infos)
{
$infos = array_merge(array(
'server' => 'localhost',
'username' => 'root',
'password' => ''), $infos);
self::$connection = mysql_connect($infos['server'], $infos['username'], $infos['password']);
if (!self::$connection) {
return false;
} else {
return true;
}
}
/**
* Déconnexion de la base de données
*
* @todo DB-Abstraction (mysql_close)
*/
public static function disconnect()
{
mysql_close(self::$connection);
}
/**
* Retourne la liste des requêtes déjà exécutées par doQuery()
* Chaque item est un tableau (requête résolue, temps d'exécution de la requête elle-même, temps d'exécution total de la fonction doQuery, message d'erreur)
*/
public static function getQueries()
{
return self::$queries;
}
/**
* Prépare une variable pour l'utilisation dans une requête SQL
* Exemples :
* SQL::escape('bonjour "Marcel" !') == '"bonjour \"Marcel\""' (utilisation de mysql_real_escape_string)
* SQL::escape(array(1, 2, 3, 4, 5)) == '(1,2,3,4,5)' (chaque variable du tableau est bien sûr échappée)
* SQL::escape(NULL) == 'NULL'
*
* @todo DB-Abstraction (mysql_real_escape_string)
*/
public static function escape($variable)
{
switch (gettype($variable)) {
case 'array':
$escaped_values = array();
foreach ($variable as $value) {
$escaped_values[] = self::escape($value);
}
return '(' . implode(',', $escaped_values) . ')';
case 'string':
return '"' . mysql_real_escape_string($variable, self::$connection) . '"';
case 'boolean':
return $variable ? 1 : 0;
case 'integer':
case 'double':
return $variable;
case 'NULL':
return 'NULL';
default: // object, resource, etc... types non gérés
return false;
}
}
/**
* Renvoie la requête préparée
* $query est la requête originale, avec des "placeholders" notés ":nom-de-clé"
* Exemple : 'SELECT name FROM users WHERE id IN :ids'
* $values est la liste des variables par lesquelles seront remplacés les "placeholders"
* Exemple : array('ids' => array(1, 3, 9))
* Exemple :
* SQL::getQuery('SELECT name FROM users WHERE id IN :ids', array('ids' => array(1, 3, 9)))
* == 'SELECT name FROM users WHERE id IN (1,3,9)'
*/
public static function getQuery($query, $values = array())
{
// On trie le tableau $values selon les index, afin d'être sûr de ne pas avoir de
// conflit (par exemple entre :user et :username)
krsort($values);
// On remplace chaque :key par self::escape($values[key])
foreach ($values as $key => $value) {
$query = str_replace(self::PLACEHOLDER_MARKER . $key, self::escape($value), $query);
}
return $query;
}
/**
* Prépare et exécute une requête, le résultat renvoyé dépend de la requête :
* - INSERT => renvoie le nombre de lignes insérées
* - UPDATE => renvoie le nombre de lignes mises à jour
* - DELETE => renvoie le nombre de lignes supprimées
* - REPLACE => renvoie le nombre de lignes affectées
* - SELECT => renvoie la liste des résultats (objets)
* - autres => renvoie la ressource non traitée
* En cas d'erreur, renvoie false
*
* @todo DB-Abstraction (mysql_query + mysql_free_result)
*/
public static function doQuery($query, $values)
{
$start_func = microtime(true);
$query = self::getQuery($query, $values);
$start_query = microtime(true);
$res = mysql_query($query, self::$connection);
$time_query = microtime(true) - $start_query;
if ($res) {
switch (strtoupper(substr(ltrim($query), 0, 7))) {
case 'INSERT ':
case 'UPDATE ':
case 'DELETE ':
case 'REPLACE':
$result = self::getAffectedRows();
mysql_free_result($res);
break;
case 'SELECT ':
$result = array();
while ($row = mysql_fetch_object($res)) {
$result[] = $row;
}
mysql_free_result($res);
break;
default:
$result = $res;
}
}
$time_func = microtime(true) - $start_func;
self::$queries[] = array($query, $time_query, $time_func, self::getError());
return $res ? $result : false;
}
/**
* Renvoie l'ID de la dernière ligne insérée (n'a de sens que s'il y a un champ
* AUTO_INCREMENT et que la dernière requête est un INSERT).
*
* @todo DB-Abstraction (mysql_insert_id)
*/
public static function getLastInsertID()
{
return mysql_insert_id(self::$connection);
}
/**
* Renvoie le nombre de lignes concernées par la dernière requête
*
* @todo DB-Abstraction (mysql_affected_rows)
*/
public static function getAffectedRows()
{
return mysql_affected_rows(self::$connection);
}
/**
* Renvoie l'erreur rencontrée lors de la dernière requête
*
* @todo DB-Abstraction (mysql_error)
*/
public static function getError()
{
return mysql_error(self::$connection);
}
}
Ce n'est pas une couche d'abstraction, pour cela il faudrait sortir les fonctions "spécifiques", faire une interface, et implémenter le design pattern factory. Ça fait ce que c'est censé faire : aider mes collègues à faire des requêtes sécurisées avec MySQL, et puis ça préparer bien le terrain pour un DBAL quand-même
Et puis les placeholders, en plus de sécuriser de manière transparente la requête, ça rend souvent les appels de requête bien plus lisibles.
Les deux fonctionnalités qui leur changent vraiment la vie ce sont les placeholders et le fait que doQuery('SELECT ...') renvoie directement la liste des résultats.
Dans la "vraie" classe c'est du PHP4, il n'y a pas de commentaires mais un readme.txt, et les temps ne sont pas loggées (juste la requête et le message d'erreur éventuel) :lol: