Gérer les accès à une base de données via une dao

Description

Bonjour,

GenDao est une source permettant de gérer les accès à la base de données sans avoir besoin d'utiliser des requêtes sql. Les opérations les plus courantes tel que la sauvegarde, la suppression, la mise à jour de données en base sont accessibles via des méthodes pré-implémentées.

Les requêtes sql sont basées sur PDO et existent en version normal et version utilisant les prepareStatement. Pour les opérations du type insert, update, delete.
Notions utilisées :

- accès au base de données via PDO
- prepareStatement PDO
- fichier de log avec apache log4php (http://logging.apache.org/log4php/index.html)
- namespace
- autoload de classes

Soit une table Post avec comme champ : id, title, body, day_2, hour_2

Exemples d'opérations possibles :

$post = Post::getById(43); //obtenir le post d'id 43
$posts = Post::getAll(); //récupérer tous les enregistrements de la table

prototype : public static function get($where = null, array $order = null, $desc = false, $limit = null)

$posts = Post::get(null,null,null,4); //récupérer les 4 premiers enregistrements
$posts = Post::get(null,null,null,array(3,7)); //récupérer les enregistrements compris entre 3 à 7 (clause sql LIMIT)

$posts = Post::get(array("day_2"=>date(date("y/m/d"))),null,null,4); //récuperer les 4 enregistrements posté aujourd'hui

$posts = Post::get(null, array("day_2","hour_2","title"),true,4);//récupérer les 4 premiers posts triées par date : les plus récents en premier

$cpt = Post::count();//compte le nombre de post

/* manipuler les données */

$post = new Post();
$post->title = "tire du post";
$post->setTitle("titre du post");
$post->save(); //sauvegarde en base -> insert
$post->body = "un texte";
$post->save(); //sauvegarde en base -> update

$post->delete();//suppression de l'objet
Post::deleteById(43);//suppression du post d'id 43

Contenu de la source :

-SqlRequest.class.php qui implémente les requêtes sql.
-Dao.class.php qui implémente les méthodes pour manipuler les objets en bases.

Pour faciliter le débogage, j'ai utilisé les log du framework log4php. Le fichier de configuration des logs se situe dans le répertoire log4php/configurators/log4php.properties. Il est paramétré en mode production, c'est à dire que les erreurs déclenchées par des exceptions sont écrite dans un fichier de log (log/globalError/error.log).

améliorations possibles :
- ajout de classes d'exception genDaoException...

Source / Exemple :


<?php
namespace GenDao;

//require_once realpath(dirname(__FILE__)).'/../config/Logger.php';
//require_once realpath(dirname(__FILE__)).'/../util/Util.class.php';
//require_once realpath(dirname(__FILE__)).'/../dal/Dal.class.php';
//require_once realpath(dirname(__FILE__)).'/../dal/DalPeer.class.php';

require_once realpath(dirname(__FILE__)).'/../model_autoload.php';

/**

  • Description of SqlRequest
*
  • @author Guillaume Genet
  • /
class SqlRequest { /*________________________________________________________________________*/ /**
  • Permet de récuperer les informations sur une table
  • @param <string> $table
  • @return <PDOStatement>
  • /
public static function showColumns($table) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = 'SHOW COLUMNS FROM '.$table; $recordset = $conn->query($query); if($recordset === false) throw new \Exception("la table ".$table." n'existe pas"); else return $recordset->fetchAll(\PDO::FETCH_ASSOC); } /*________________________________________________________________________*/ /**
  • Permet de tester si la table existe
  • @param <string> $table
  • @return <bool>
  • /
public static function existTable($table) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = 'DESC '.$table; $recordset = $conn->query($query); return ($recordset instanceof \PDOStatement); } /*________________________________________________________________________*/ /** */ /**
  • Permet de tester si la colonne existe dans la table
  • @param <string> $table le nom de la table
  • @param <string> $field nom du champ
  • @return <boolean>
  • /
public static function existColumns($table, $field) { Util::emptyException($table); Util::emptyException($field); $field = strtolower(trim($field)); $champTable = static::showColumns($table); foreach ($champTable as $tableField) { if(strtolower(trim($tableField['Field'])) == $field) return true; } return false; } /*________________________________________________________________________*/ /**
  • permet de genérer la clause where/set
  • @param array $clause tableau ex:array('ville'=>'clermont-ferrand','nom'=>'genet') --> ville = 'clermont-ferrand' and nom='genet'
  • @param PDO $conn
  • @return <string> la chaine sql
  • /
public static function clause(array $clause, PDO $conn = null) { Util::nullException($clause); $query = ""; $and = 0; if($conn === null) $conn = DalPeer::getConnection(); foreach($clause as $element => $valeur) { $temp = ""; if($and === 1) $temp .= " and "; $temp .= $element."="; if (is_numeric($valeur)) $temp .= $valeur; else $temp .= $conn->quote($valeur,\PDO::PARAM_STR); //rajoute les quotes et protège la requête sql en ajoutant les \ $and = 1; $query .= $temp; } return $query; } /*________________________________________________________________________*/ protected static function getClauseWhere($where,$conn = null) { if(empty($where)) return ""; if($conn === null) $conn = DalPeer::getConnection(); $query = ""; $query .= ' WHERE '; if(is_array($where)) { $query .= static::clause($where); } elseif(is_string($where)) { $query .= $conn->quote($where,\PDO::PARAM_STR); //rajoute les quotes et protège la requête sql en ajoutant les \ } else throw new \Exception("La clause where doit être un string ou un tableau"); return $query; } /*________________________________________________________________________*/ /**
  • Contruit la requete insert
  • @param <string> $table le nom de la table
  • @param array $elements
  • @return <int> l'identifiant de la ligne insérée
  • /
public static function insert($table, array $elements) { Util::emptyException($table); Util::emptyException($elements); $conn = DalPeer::getConnection(); $keys = array_keys($elements); $values = array_values($elements); $query = "INSERT INTO " . $table . " ("; $query .= implode(', ',$keys); $query .= ") values ("; $countValues = count($values); for ($i = 0; $i < $countValues; $i++) //ajout des valeurs { if (is_numeric($values[$i])) $query .= $values[$i]; else $query .= $conn->quote($values[$i],\PDO::PARAM_STR); //pour protéger la requête sql if ($i != count($values) - 1) { $query .= ", "; } } $query .= " )"; $flag = $conn->exec($query); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('insert: '.$query.' --> nbLigneInsert='.$flag); if($flag === 1) $flag = $conn->lastInsertId(); //on récupère l'id else if($flag === false) throw new \Exception("erreur lors de la reqête insertion - erreur:".Util::printPdoError($conn->errorInfo())); return $flag; } /*________________________________________________________________________*/ /**
  • Contruit la requete insert en utilisant un prepareStatement
  • @param <string> $table le nom de la table
  • @param <array> $elements
  • @return <int> l'identifiant de la ligne insérée
  • /
public static function insertPrepareStatement($table, array $elements) { Util::emptyException($table); Util::emptyException($elements); $conn = DalPeer::getConnection(); $fieldNames = array_keys($elements); //recupère le nom des champs $query = 'INSERT INTO '.$table; $fields = '( ' . implode(' ,', $fieldNames) . ' )'; $values = '(:' . implode(', :', $fieldNames) . ' )'; $query .= $fields.' VALUES '.$values; $stmt = $conn->prepare($query); $flag = $stmt->execute($elements); $stmt->closeCursor(); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('insert: '.$query.' --> nbLigneInsert='.$flag); if($flag === true) $flag = $conn->lastInsertId(); //on récupère l'id else if($flag === false) throw new \Exception("erreur lors de la reqête insertion - erreur:".Util::printPdoError($conn->errorInfo())); return $flag; } /*________________________________________________________________________*/ /**
  • Contruit la requete update
  • @param <string> $table
  • @param <array> $update
  • @param <array|string> $where
  • @return <int> le nombre de ligne modifiée
  • /
public static function update($table, array $update, $where) { Util::emptyException($table); Util::emptyException($update); Util::emptyException($where); $conn = DalPeer::getConnection(); $query = 'UPDATE '.$table.' SET '; $i=0; foreach ($update as $key => $value) { $query .= $key.'='.$conn->quote($value,\PDO::PARAM_STR); if ($i != count($update) - 1) { $query .= ", "; } $i++; } $query .= static::getClauseWhere($where); $flag = $conn->exec($query); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('update: '.$query.' --> nbLigneUpdate='.$flag); if($flag === false) throw new \Exception("erreur lors de la reqête update - erreur:".Util::printPdoError($conn->errorInfo())); return $flag; } /*________________________________________________________________________*/ /**
  • Contruit la requete update
  • @param <string> $table
  • @param <array> $update
  • @param <array> $where
  • @return <int> le nombre de ligne modifiée
  • /
public function updatePrepareStatement($table, array $update, array $where) { Util::emptyException($table); Util::emptyException($update); Util::emptyException($where); $conn = DalPeer::getConnection(); $query = 'UPDATE '.$table.' SET '; $updateKeys = array_keys($update); $whereKeys = array_keys($where); $countUpdate = count($updateKeys); for($i = 0; $i < $countUpdate ; $i++) //construction de la première partie set titre=:titre, message=:message { $query.= $updateKeys[$i]." = :".$updateKeys[$i]; if($i != $countUpdate-1) $query.=", "; } $query .= " WHERE "; $countWhere = count($whereKeys); for($i = 0; $i < $countWhere ; $i++) { $query.=$whereKeys[$i]." = :".$whereKeys[$i]; if($i != $countWhere-1) $query .= " and "; } $args = array_merge($update, $where);//on fusionne les 2 tableaux pour le prepareStatement $sth = $conn->prepare($query); $flag = $sth->execute($args); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('update: '.$query.' --> nbLigneUpdate='.$flag); if($flag === false) throw new \Exception("erreur lors de la reqête update - erreur:".Util::printPdoError($conn->errorInfo())); return $flag; } /*________________________________________________________________________*/ /**
  • Contruit la requete delete
  • @param <string> $table nom de la table
  • @param array|string $where
  • @return <int> le nombre de ligne supprimée
  • /
public static function delete($table, $where = null) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = 'DELETE FROM '.$table; $query .= static::getClauseWhere($where); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); $flag = $conn->exec($query); if($logger->isDebugEnabled()) $logger->debug('delete: '.$query." --> nbLigneDelete=".$flag); if($flag === false) throw new \Exception("erreur lors du delete de l'objet en base - erreur:".Util::printPdoError($conn->errorInfo())); return $flag; } /*________________________________________________________________________*/ /**
  • construit la requete sql select
  • @param <string> $table
  • @param array $elements
  • @param <array|string> $where
  • @param <array> $order
  • @param <bool> $desc
  • @param <int|array> $limit
  • @return <array<stdclass>> la liste des enregistrements
  • /
public static function select($table, array $elements = null, $where = null,array $order = null,$desc = null, $limit = null) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = "SELECT "; if(empty($elements)) $query .= " * "; else $query .= implode(",", $elements); $query .= " FROM " . $table; $query .= static::getClauseWhere($where); if(!empty($order)) { $query .= " ORDER BY "; $countOrder = count($order); for ($i = 0; $i < $countOrder; $i++) { $query .= $order[$i]; if($desc !== null && $desc === true) $query .=" DESC "; if ($i < count($order) - 1) { $query .= ", "; } } } if($limit !== null ) { if(is_array($limit)) { if(count($limit) === 2) $query .= " LIMIT ".$limit[0].", ".$limit[1]; else throw new \Exception("erreur: 2 arguments attendus pour LIMIT"); } else $query .= " LIMIT ".$limit; } $recordset = $conn->query($query); $result = null; if($recordset === false) throw new \Exception("erreur lors de la reqête :".$query." - erreur:".Util::printPdoError($conn->errorInfo())); else { $recordset->setFetchMode(\PDO::FETCH_OBJ); $result = $recordset->fetchAll(); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('select: '.$query." --> result: ".Util::get_print_contents($result)); } return $result; } /*______________________________________________________________________________________________________________________________*/ /**
  • construit la requete sql select avec un prepareStatement
  • @param <string> $table
  • @param array $elements
  • @param <array> $where
  • @param <array> $order
  • @param <bool> $desc
  • @param <int|array> $limit
  • @return <array<stdclass>> la liste des enregistrements
  • /
public function selectPrepareStatement($table, array $elements = null,array $where = null, array $order = null,$desc = null, $limit = null) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = "SELECT "; if(empty($elements)) $query .= " * "; else { $countInsert = count($elements); for($i = 0; $i < $countInsert ; $i++) //construction de la première partie set titre=:titre, message=:message { $query.= $elements[$i]; if($i != $countInsert-1) $query.=", "; } } $query .= " FROM " . $table; if(!empty($where)) { $query .= " WHERE "; $whereKeys = array_keys($where); $countWhere = count($whereKeys); for($i = 0; $i < $countWhere ; $i++) { $query.=$whereKeys[$i]." = :".$whereKeys[$i]; if($i != $countWhere-1) $query .= " and "; } } if(!empty($order)) { $query .= " ORDER BY "; $countOrder = count($order); for ($i = 0; $i < $countOrder; $i++) { $query .= $order[$i]; if($desc !== null && $desc === true) $query .=" DESC "; if ($i < count($order) - 1) { $query .= ", "; } } } if($limit !== null ) { if(is_array($limit)) { if(count($limit) === 2) $query .= " LIMIT ".$limit[0].", ".$limit[1]; else throw new \Exception("erreur: 2 arguments attendus pour LIMIT"); } else $query .= " LIMIT ".$limit; } $sth = $conn->prepare($query); $flag = $sth->execute($where); if($flag === false) throw new \Exception("erreur lors de la reqête select : $query - erreur:".Util::printPdoError($sth->errorInfo())); $sth->setFetchMode(\PDO::FETCH_OBJ); $result = $sth->fetchAll(); $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('select: '.$query." --> result: ".Util::get_print_contents($result)); return $result; } /*________________________________________________________________________*/ /**
  • Execute une requete sql
  • @param <type> $query
  • @return <PDOStatement> recordset
  • /
public static function query($query) { Util::emptyException($query); $conn = DalPeer::getConnection(); $recordset = $conn->query($query); $result = null; $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('query_object: '.$query." --> result: ".Util::get_print_contents($result)); if($recordset === false) throw new \Exception("erreur lors de l'execution de la reqête:".$query." - erreur:".Util::printPdoError($conn->errorInfo())); return $recordset; } /*________________________________________________________________________*/ /* execute une requete sql et renvoi un tableau de tableau associatif*/ public static function query_assoc($query) { $recordset = static::query($query); $recordset->setFetchMode(\PDO::FETCH_ASSOC); $result = $recordset->fetchAll(); return $result; } /*________________________________________________________________________*/ /* execute une requete sql et renvoi un tableau d'objet*/ public static function query_object($query) { $recordset = static::query($query); $recordset->setFetchMode(\PDO::FETCH_OBJ); $result = $recordset->fetchAll(); return $result; } /*________________________________________________________________________*/ /* constuit la requete count */ public static function count($table,$where = null) { Util::emptyException($table); $conn = DalPeer::getConnection(); $query = "SELECT COUNT(*) as total FROM ".$table; $query .= static::getClauseWhere($where); $recordset = $conn->query($query); if($recordset !== false) { $result = $recordset->fetch(\PDO::FETCH_ASSOC); return $result["total"]; } else if(!static::existTable($table)) //pour aider le debogage throw new \Exception("la table ".$table." n'existe pas"); else throw new \Exception("erreur lors de l'execution de la reqête count - erreur:".Util::printPdoError($conn->errorInfo())); } } ?> /****************** la DAO ********************/ <?php namespace GenDao; /**
  • Description of Dao
*
  • @author Guillaume Genet
  • /
require_once realpath(dirname(__FILE__)).'/../model_autoload.php'; class Dao { /** Correspond à la clé primaire de la base de données */ public static $ID = "id"; /** tableau associatif permettant de stocker les champs issus de la base de données*/ public $attributs = null; /** le nom de la classe source*/ public $className = null; public $isNewInstance = false; public $isDeleted = false; /*________________________________________________________________________*/ public static function getPrimaryKeyField() { return self::$ID; } /*________________________________________________________________________*/ protected function setAttributs(array $attributs) { $this->attributs = $attributs; } /*________________________________________________________________________*/ protected function initFields() { $champTable = SqlRequest::showColumns($this->className); if($champTable != null) { //construction des attributs de la classe if($this->attributs == null) foreach ($champTable as $field) { $temp = strtolower($field['Field']); $this->attributs[$temp] = null; } } else throw new \Exception($this->className." n'existe pas dans la base de données"); } /*________________________________________________________________________*/ /** constructeur */ public function __construct($args=null) { //SqlRequest::query('SET NAMES utf8'); $this->attributs = array(); $this->className = get_called_class(); $this->isNewInstance = true; $this->isDeleted = false; $this->initFields(); } /*________________________________________________________________________*/ public function isNew() { return $this->isNewInstance; } /*________________________________________________________________________*/ protected function setNewInstance($bool) { $this->isNewInstance = $bool; } /*________________________________________________________________________*/ public function isDeleted() { return $this->isDeleted; } /*________________________________________________________________________*/ protected function setDeleted($bool) { $this->isDeleted = $bool; } /*________________________________________________________________________*/ public function getClassName() { return $this->className; } /*________________________________________________________________________*/ /* constuit la requete count */ public static function count() { return SqlRequest::count(get_called_class()); } /*________________________________________________________________________*/ /**
  • Permet de convertir un objet de type stdClass(PDO) en class courante
  • @param stdClass $stdClass
  • @return <Object>
  • /
public static function convertToClass(\stdClass $stdClass) { $class = get_called_class(); $dynamicClass = new $class; //nouvelle instance de classe $dynamicClass->setNewInstance(false); //convertion foreach ($stdClass as $champ=>$valeur) { $dynamicClass->$champ = $valeur; } return $dynamicClass; } /*________________________________________________________________________*/ /**
  • Permet de récuperer l'objet en fonction de l'identifiant
  • @param <type> $id
  • @return <Object> une instance de classe
  • /
public static function getById($id) { $result = SqlRequest::selectPrepareStatement(get_called_class(),null,array(self::$ID=>$id)); if(!empty($result)) { $count = count($result); if($count === 1) return self::convertToClass($result[0]); else if($count > 1) throw new \Exception("l'identifiant ".$id." est présent plusieurs fois dans la base de données"); } return null; } /*________________________________________________________________________*/ /* fait un select * sur la table */ public static function getAll() { $stmt = SqlRequest::query("select * from ".get_called_class()); //$stmt->setFetchMode(\PDO::FETCH_CLASS,get_called_class(),null); $stmt->setFetchMode(\PDO::FETCH_OBJ); $result = $stmt->fetchAll(); foreach ($result as $line) //parcour des enregistrements { $listObjects[] = self::convertToClass($line); } $stmt -> closeCursor(); return $listObjects; } /*________________________________________________________________________*/ /**
  • Permet de récuperer tous les objets en fonction des paramètres
  • @param <array|string> $where la clause where de la requête
  • @param <numeric|array> $limit le nombre d'enregistrement envoyé
  • @param <array> $order un array contenant les champs de table, permet de trier les éléments
  • @param <boolean> $desc vrai = tri décroissant, tri croissant sinon
  • @return <array> un array contenant les objets
  • /
public static function get($where = null, array $order = null, $desc = false, $limit = null) { $listObjects = array(); $recordset = SqlRequest::selectPrepareStatement(get_called_class(),null,$where,$order,$desc,$limit); foreach ($recordset as $line) //parcour des enregistrements { $listObjects[] = self::convertToClass($line); } return $listObjects; } /*________________________________________________________________________*/ /** Permet de sauvegarder l'objet */ public function save() { $newInstance = $this->isNew(); $dal = DalPeer::getConnection(); $flag = 0; if($newInstance)//on fait un insert en base { $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('doSave:insert'); $flag = SqlRequest::insert($this->className, $this->attributs); if($flag >= 0) { $this->attributs[self::$ID] = $flag; //ajout de l'attribut id $this->setNewInstance(false); } } else //on fait un update en base { if(!$this->isDeleted()) { $logger = \Logger::getLogger(basename(dirname(__FILE__)).'.'.__CLASS__); if($logger->isDebugEnabled()) $logger->debug('doSave:update'); $flag = SqlRequest::update($this->className, $this->attributs, array(self::$ID=>$this->attributs[self::$ID])); } else throw new \Exception("erreur: impossible de sauvegarder des données préalablement supprimées"); } return $flag; } /*________________________________________________________________________*/ /** Permet de supprimer l'objet */ public static function deleteById($id) { $dal = DalPeer::getConnection(); $flag = SqlRequest::delete(get_called_class(),array(self::$ID => $id)); return $flag; } /*________________________________________________________________________*/ /** Permet de supprimer l'objet */ public function delete() { $dal = DalPeer::getConnection(); $flag = 0; if(!$this->isDeleted()) { $flag = SqlRequest::delete($this->className,array(self::$ID => $this->attributs[self::$ID])); $this->setDeleted(true); } else throw new \Exception("erreur:appel de la fonction delete plusieurs fois à la suite"); return $flag; } /*________________________________________________________________________*/ /**
  • Permet d'accéder aux attributs de la classe (sans l'utilisation d'un getter ex: echo $article->titre;)
  • @param <string> $name le nom de l'attribut
  • @return <array> le tableau de résultat
  • /
public function __get($name) { $name = strtolower($name); if(array_key_exists($name, $this->attributs)) { return $this->attributs[$name]; } else throw new \Exception( "l'attribut ".$name. " n'existe pas pour l'objet ".$this->className); } /*________________________________________________________________________*/ /** Permet d'accéder aux attributs de la classe (sans l'utilisation d'un setter ex: $article->titre='football')
  • @param <string> $name le nom de l'attribut
  • @param <object> $value
  • @return <array> le tableau de résultat
  • /
public function __set($name, $value) { $name = strtolower($name); if(array_key_exists($name, $this->attributs))//pour pas ajouter un element au tableau assosiatif { $this->attributs[$name] = $value; } else throw new \Exception("l'attribut ".$name. " n'existe pas pour l'objet ".$this->className); } /*________________________________________________________________________*/ /** appel de méthode */ public function __call($name, $arguments) { $getterSetter = strtolower(substr($name, 0,3)); //pour accéder aux attributs en utilisant les getters if($getterSetter === "get") { $attribut = strtolower(substr($name, 3));//nom de l'attribut return $this->__get($attribut); } //pour affecter des attributs en utilisant les setters else if($getterSetter === "set") { $attribut = strtolower(substr($name, 3)); $this->__set($attribut, $arguments[0]); } else throw new \Exception($name." est une méthode inconnue"); } } ?>

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.