Accès aux données
Posté : 13 mai 2005, 19:08
Bonjour,
j'ai pas mal travaillé sur mon dernier projet en PHP5 sur l'accès aux données. Suite à ce travail je me suis dit tout d'abord qu'il serait intéressant de partager mon expérience, et j'ai donc proposé à Damien d'écrire un tutoriel. Mais avec un peu de recul je me suis dit que cela était prématuré et un peu prétentieux de ma part étant donné mon expérience.
Ayant discuté avec Cyrano hier, notament sur ce thème, je lui ai exprimé mon désir d'approfondir ce sujet, et s'il pensait que cela était une bonne idée de lancer une discussion à ce sujet. Il m'a suggéré de faire cela dans le forum des développeurs.
Me voilà donc et j'espère avoir vos réactions.
La première idée c'est créer une couche d'abstraction à la base de données, afin de ne pas rendre son code dépendant d'un SGBD spécifique. J'ai donc créé une interface ISgbd qui spécifie toutes les méthodes que l'on doit attendre d'un SGBD :
Une classe abstraite qui sera héritée par toutes les classes qui permettent l'accès aux données. Chaque classe (ou DAO) représente une table en base de données.
Ici se rejoignent l'abstraction à la base et l'accès aux données, car le DAO reçoit en paramètre de son constructeur un objet de type ISgbd. Il n'est donc pas dépendant d'une base.
Après, pour la mise en oeuvre de tout cela, il y a sûrement plusieurs possibilités, la plus simple étant à mon avis de laisser en état. Ex :
Voilà, j'espère que ce n'est pas trop confus. J'aimerais donc obtenir vos remarques sur ce qui parait mal conçu, inefficace,... donc surtout au niveau conception dans un premier temps (la gestion des erreurs n'est pas bien faites et sera a améliorer par la suite).
merci
daoud
j'ai pas mal travaillé sur mon dernier projet en PHP5 sur l'accès aux données. Suite à ce travail je me suis dit tout d'abord qu'il serait intéressant de partager mon expérience, et j'ai donc proposé à Damien d'écrire un tutoriel. Mais avec un peu de recul je me suis dit que cela était prématuré et un peu prétentieux de ma part étant donné mon expérience.
Ayant discuté avec Cyrano hier, notament sur ce thème, je lui ai exprimé mon désir d'approfondir ce sujet, et s'il pensait que cela était une bonne idée de lancer une discussion à ce sujet. Il m'a suggéré de faire cela dans le forum des développeurs.
Me voilà donc et j'espère avoir vos réactions.
La première idée c'est créer une couche d'abstraction à la base de données, afin de ne pas rendre son code dépendant d'un SGBD spécifique. J'ai donc créé une interface ISgbd qui spécifie toutes les méthodes que l'on doit attendre d'un SGBD :
interface ISgbd {
/*
* Effectue une requête sur la base de données.
*/
public function query($sql);
/*
* Retourne l'enregistrement du resultset
*/
public function getRow();
/**
* Retourne un enregistrement sous forme d'objet
* @return object
*/
public function getObject();
/*
* Retourne un enregistrement sous forme de tableau
*/
public function getArray();
/*
* Retourne un enregistrement sous forme de tableau associatif
*/
public function getAssoc();
/*
* Retourne le nombre d'enregistrements du resultset
*/
public function getNumRows();
/*
* retourne une valeur formattée pour être intégrée au SGBD
*/
public function getSQLValue($value, $type);
/*
* Retourne l'id du dernier élément inséré
*/
public function getLastInsertId();
}
Ensuite pour chaque SGBD on réalise cette interface, ici pour mysql avec mysql_... :
class MysqlDb implements ISgbd{
private $cnx = null;
private $result = null;
/*
* Constructeur. Démarrage de la connexion.
*/
function __construct($server, $user, $password, $base){
$this->cnx = mysql_connect($server, $user, $password);
if($this->cnx == false){
die("Impossible d'ouvrir la connexion à la base");
}
mysql_select_db($base, $this->cnx);
}
/*
* Effectue une requête sur la base de données.
*/
public function query($sql){
$this->result = false;
if($this->cnx != false){
try {
$this->result = mysql_query($sql, $this->cnx);
}catch (Exception $e) {
echo "erreur : " . $e->getMessage();
}
}
return $this->result;
}
/*
* Retourne l'enregistrement du resultset
*/
public function getRow() {
$row = null;
if($this->result != false)
$row = mysql_fetch_row($this->result);
return $row;
}
...
}
Ensuite pour l'accès aux données, à partir du design pattern DataAccessObject je propose la structure suivante :Une classe abstraite qui sera héritée par toutes les classes qui permettent l'accès aux données. Chaque classe (ou DAO) représente une table en base de données.
abstract class AbstractDAO {
/**
* le type du sgbd, un objet de type IDb
*/
protected $db;
/*
* Le nom de la table
*/
protected $nomTable;
/*
* La liste des champs sous forme de tableau
*/
protected $arrayChamp;
/*
* La liste des types sous forme de tableau
*/
protected $arrayType;
/*
* Cette méthode permet d'insérer un enregistrement dans la table,
* l'id est inséré automatiquement par le sgbd
*/
public function insertAutoNum($arrayVal){
$sql = "INSERT INTO " . $this->nomTable . " (";
for($i = 0; $i < count($this->arrayChamp); $i++){
if($i != 0){
if($i != 1)
$sql .= ",";
$sql .= $this->arrayChamp[$i];
}
}
$sql .= ") VALUES(";
for($i = 0; $i < count($arrayVal); $i++){
if($i != 0)
$sql .= ",";
$sql .= $this->db->getSQLValue($arrayVal[$i], $this->arrayType[$i + 1]);
}
$sql .= ")";
$this->db->query($sql);
return $this->db->getLastInsertId();
}
/*
* Cette méthode permet d'insérer un enregistrement dans la table
* l'id est entré manuellement
*/
public function insertAvecId($arrayVal){
$sql = "INSERT INTO " . $this->nomTable . " (";
for($i = 0; $i < count($this->arrayChamp); $i++){
if($i != 0)
$sql .= ",";
$sql .= $this->arrayChamp[$i];
}
$sql .= ") VALUES(";
for($i = 0; $i < count($arrayVal); $i++){
if($i != 0)
$sql .= ",";
$sql .= $this->db->getSQLValue($arrayVal[$i], $this->arrayType[$i + 1]);
}
$sql .= ")";
$this->db->query($sql);
return $this->db->getLastInsertId();
}
/*
* Retourne un tableau associatif
*/
public function select($criteres){
$sql = "SELECT * FROM " . $this->nomTable;
$where = "";
if(count($criteres != 0)){
$where = " WHERE 1 ";
foreach($criteres as $critere){
$where .= " AND " . $critere;
}
}
$orderBy = " ORDER BY " . $this->arrayChamp[0] . " DESC";
$this->db->query($sql . $where . $orderBy);
$assoc = $this->db->getAssoc();
if($assoc == false)
return null;
else
return $assoc;
}
/*
* Efface tous les enregistrements correspondants aux critères
*/
public function delete($criteres){
$sql = "DELETE FROM " . $this->nomTable;
$where = "";
if(count($criteres) != 0){
$where = " WHERE 1 ";
foreach($criteres as $critere){
$where .= " AND " . $critere;
}
}
$this->db->query($sql . $where);
}
etc...
}
Puis chaque DAO hérite de cette classe. ex :
class SiteDAO extends AbstractDAO{
function __construct(ISgbd $db){
$this->db = $db;
$this->nomTable = "Site";
$this->arrayChamp = array("idSite", "libelle", "domaine");
$this->arrayType = array("int", "text", "text");
}
}
Ainsi avec chaque objet qui hérite de AbstractDAO on peut faire toutes les méthodes de base que l'on a défini. Ensuite on ajoute dans chaque classe DAO les méthodes dont on peut avoir besoin, des méthodes spécifiques (notament pour les requêtes avec jointure).Ici se rejoignent l'abstraction à la base et l'accès aux données, car le DAO reçoit en paramètre de son constructeur un objet de type ISgbd. Il n'est donc pas dépendant d'une base.
Après, pour la mise en oeuvre de tout cela, il y a sûrement plusieurs possibilités, la plus simple étant à mon avis de laisser en état. Ex :
$database = new MysqlDb($server, $user, $password, $base);
$siteDAO = new SiteDAO($database);
$siteDAO->select(array("idSite=1", "domaine='www.phpfrance.com'"));
Dans mon projet j'ai utilisé le design pattern factory, qui permet de déléguer à une classe la création des objets DAO, à voir.Voilà, j'espère que ce n'est pas trop confus. J'aimerais donc obtenir vos remarques sur ce qui parait mal conçu, inefficace,... donc surtout au niveau conception dans un premier temps (la gestion des erreurs n'est pas bien faites et sera a améliorer par la suite).
merci
daoud