Datasource : abstraction de données

Contenu du snippet

Voici 4 classes php5 écrites dans le but d'utiliser indépendemment de son programme une source de données(fichier ou bdd).
Pour utiliser un fichier xml on fait créer à la Datasource l'objet simplexml et pour une bdd : le provider correspondant
La source de donnée ici est très simplifiée :une classe abstraite décrivant la source de donnée et 3 classes filles implémentant cette même source.
Pour exemple une classe XML , une classe Mysql, une classe Sqlite sont dérivées de Datasource.
Bon le code est toujours extensible (j'ai récupéré quelques codes à doite, à gauche) d'ailleurs surtout à gauche :), j'aime pas trop le bleu.
Rassurez - vous, rien de sous entendu!

Source / Exemple :


<!--
Fichier XML test3.xml
-->
<?xml version="1.0" ?> 
<a>
 <b>
  <c id="test" >text</c>
  <c id="test2" name="oups" >stuff</c>
 </b>
 <d>
  <c>code</c>
 </d>
</a>
<!--
Fin Fichier XML test3.xml
-->
<?php
/**

  • Classe ERROR
  • Description:
  • Gestion des erreurs
  • /
class ERROR { const ERROR_OFF=0; const ERROR_DISPLAY=1; const ERROR_LOGFILE=2; const NOTICE=0; const WARNING=1; const FATAL=2; const IGNORE=3; public static $LOGFILE='log/logfile.log'; protected static $mode=self::ERROR_DISPLAY; public static function set($mode) { self::$mode = $mode; } /**
  • Process statique de getion d'erreurs
  • dependant du mode ->
  • display : affichage
  • logfile : enregistrement des erreurs dans le fichier logfile
  • off : erreurs gérées par php uniquement
  • /
public static function procError ($type,$class,$msg) { if (self::$mode === self::ERROR_DISPLAY) { switch($type) { case self::FATAL: throw new Exception ('<br/><strong> ERREUR Fatale : '.$class.' - '.$msg.'<br/><br/></strong>'); break; case self::IGNORE: break; case self::WARNING: echo '<br/><strong>WARNING : '.$class.' - '.$msg.'.<br/><br/></strong>'; break; case self::NOTICE: echo '<br/><strong>NOTICE: '.$class.' - '.$msg.'.<br/><br/></strong>'; break; default: throw new Exception ('<strong>ERR: type d\'erreur innatendu : '.$type.' - '.$class.' - '.$msg.'</strong>'); } } else if (self::$mode === self::ERROR_OFF) { //Todo } else if (self::$mode === self::ERROR_LOGFILE) { if (file_exists(self::$LOGFILE)&&is_writeable(self::$LOGFILE)) { $fp = fopen (self::$LOGFILE,'a'); if (flock($fp, LOCK_EX)) { switch($type) { case self::FATAL: fputs($fp,date('d/m/Y H:i:s').' : ERREUR Fatale : '.$class.' - '.$msg."\n"); flock($fp, LOCK_UN); fclose($fp); throw new Exception ('ERREUR Fatale : '.$class.' - '.$msg); break; case self::IGNORE: break; case self::WARNING: fputs($fp,date('d/m/Y H:i:s').' :WARNING : '.$class.' - '.$msg."\n"); break; case self::NOTICE: fputs($fp,date('d/m/Y H:i:s').' :NOTICE: '.$class.' - '.$msg."\n"); break; default: fputs($fp,date('d/m/Y H:i:s').' :ERREUR Innattendue : '.$class.' - '.$msg."\n"); flock($fp, LOCK_UN); fclose($fp); throw new Exception ('ERREUR Innattendue : '.$class.' - '.$msg); } flock($fp, LOCK_UN); } else throw new Exception('ERR: Le fichier log '.self::$LOGFILE.' est vérrouilé en écriture'); fclose($fp); } else throw new Exception('ERR: Problème rencontré los de la création du fichier '.self::$LOGFILE); } } } /**
  • CDataSource
  • Description : Intéraction avec plusieurs sources de données différentes
  • comme une base de données, un fichier xml, ou autre
  • emploi :CDataSource::add(type,arg1,argn)
  • argn sont les arguments requis pour le constructeur de classe type.
  • /
abstract class CDataSource { /** history of succeeded query
  • /
public $history; /** history of error query
  • /
public $errHistory; protected $errQuery_id=-1; /** id de requete
  • /
protected $query_id=-1; /**
  • tableau contenant les paramètres de connexion
  • /
protected $_aConfig=null; /**
  • flag property
  • /
protected $_bConnected=false; /**
  • ressource
  • /
public $_ressource=null; /**
  • flag bench
  • /
protected $_BENCH = false; /**
  • constructeur
  • /
protected function __construct() { } /**
  • membre abstrait
  • /
abstract protected function checkConfiguration(); /**
  • membre abstrait
  • /
abstract public function query($queryString, $desc=NULL); /**
  • membre abstrait
  • /
abstract public function fetch_assoc(); /**
  • membre abstrait
  • /
abstract public function free(); /**
  • membre abstrait
  • /
abstract public function num_rows(); /** membre abstrait
  • /
abstract public function insert_id(); /** membre abstrait
  • /
abstract public function error(); /**
  • Methode add :
  • Description : instancie un objet de class sourceType
  • ses arguments sont variables selon le type construit
  • args: nombre arguments variables.
  • add(familyClass,argumentConstructeur1,argumentConstructeur2,...,argumentConstructeurN)
  • ex:CDataSource::add(CMysql,'localhost','user','pwd','dbname');
  • /
public function add($sourceType) { if (class_exists($sourceType)) { if (is_subclass_of($sourceType,__CLASS__)) { if(($n=func_num_args())>1) { $args=func_get_args(); $s='$args[1]'; for($i=2;$i<$n;++$i) $s.=",\$args[$i]"; eval("\$component=new $sourceType($s);"); $component->connect(); return $component; } else return new $sourceType; } else throw new Exception ('Impossible d\instancier la classe '.$sourceType); } else throw new Exception ('La classe '.$sourceType.' est inexistante'); } /**
  • A implémenter
  • /
public function init() { } /**
  • A implémenter
  • /
protected function connect() { } /**
  • La connexion à la source a-t'elle lieu?
  • /
public function isConnected() { if ( $this->_bConnected ) return true; return false; } public function setBench($value) { if (is_bool($value)) { $this->_BENCH = $value; } else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': type bool attendu <br/>'); } } /**
  • CSimpleXml
  • Description :
  • Source de données de type fichier xml
  • /
class CSimpleXml extends CDataSource { public $result=null; private $arTmp=null; /**
  • Constructeur
  • /
protected function __construct ($fileName) { $this->_aConfig['FILENAME'] = $fileName; $this->_bConnected=false; } /** Vérifie et retourne si les noeuds xml existent, l'element SimpleXML correspondant
  • /
public function __get($prop) { //echo $prop.'</br>'; if (property_exists($this->_ressource,$prop)) return $this->_ressource->$prop; else throw new Exception(get_class($this->_ressource).' : Paramètre Invalide: '.$prop); } /**
  • Vérifiaction de la configuration
  • /
protected function checkConfiguration() { if (is_array ($this->_aConfig)) { if (isset($this->_aConfig['FILENAME'])) $tmp = $this->_aConfig['FILENAME']; else $tmp=''; if (!is_file($tmp)) return false; if ( strtolower ( array_pop( explode( '.',$tmp) ) ) != 'xml' ) return false; return true; } return false; } /**
  • Vérification de la configuration + parsing + connexion
  • /
protected function connect() { if ( $this->checkConfiguration() ) { $resParser = xml_parser_create(); if (xml_parse($resParser,file_get_contents($this->_aConfig['FILENAME']))) { $this->_ressource = simplexml_load_file($this->_aConfig['FILENAME']); } else { $errorStr = xml_error_string( xml_get_error_code($resParser)).' at LINE '.xml_get_current_line_number ($resParser); ERROR::procError( ERROR::FATAL, get_class(),'::'.__FUNCTION__.':'. $this->_aConfig['FILENAME'].': Fichier xml non valide: '.$errorStr); } xml_parser_free($resParser); } else ERROR::procError( ERROR::FATAL, get_class(),'::'.__FUNCTION__.':Erreur de configuration de l\'objet de type: '.__CLASS__); $this->_bConnected = true; } /**
  • Requête générique
  • /
public function query($queryString, $desc=NULL,$error_level=ERROR::WARNING) { unset($this->arTmp); $this->arTmp=null; if ($this->_BENCH) $start = microtime (true); $this->result = $this->_ressource->xpath($queryString); if ($this->_BENCH) $query_time = microtime (true) - $start; else $query_time =null; if ($this->result) { $this->history[++$this->query_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time); return $this->result; } else { ERROR::procError($error_level,get_class(),'::'.__FUNCTION__.': Xpath Error in '.$queryString.' at line '.__LINE__ ); if (count($this->history)>0) $this->result = $this->_ressource->xpath ($this->history[$this->query_id]['query']); $this->errHistory[++$this->errQuery_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time, 'queryPos'=>$this->query_id+1); return false; } } /**
  • Retourne ligne par ligne le resultat de la requête sous forme de tableau
  • associatif
  • /
public function fetch_assoc() { if(is_null($this->arTmp)) $this->fetch_assoc_ar(); if (current($this->arTmp)) { $arr = current ($this->arTmp) ; next ($this->arTmp); return $arr; } return null; } /**
  • /
public function num_rows() { return count($this->arTmp); } /** A implémenter dans le cadre de SimpleXML
  • /
public function error() { return; } /**
  • /
public function insert_id() { return null; } /** construit le tableau temporaire à dispo de fecth_assoc
  • /
private function fetch_assoc_ar() { $i=0; //unset($this->arTmp); //$this->arTmp=null; if (!$this->result) ERROR::procError(ERROR::FATAL,get_class(),'::'.__FUNCTION__.': Aucune requête n\'a été définie <br/>'); if ($this->query_id > -1) { $node=explode('/',$this->history[$this->query_id]['query']); if (isset($node[0])&&empty($node[0])) { array_shift($node); array_shift($node); } //print_r($node); $newnode=$this; foreach ($node as $prop) { $newnode=$newnode->$prop; } $aVal=array(); foreach ($newnode as $key=>$val) { foreach ( $val->attributes() as $attr=>$value ) { $idx=(string)$attr; $aVal[$i][$idx]=(string)$value; } $aVal[$i]['TagValue']=(string)$val; $i++; } $this->arTmp = $aVal; } else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': Aucune requête n\'a été définie <br/>'); return NULL; } /**
  • libére la resource resultat
  • /
public function free() { unset ($this->result); } /**
  • clot la connexion
  • /
public function close(){ unset ($this->_ressource); $this->_bConnected=false; } /**
  • recharge la classee à partir de la dernière configuration
  • /
public function reload(){ $this->connect(); } } /**
  • CMysql
  • Description: La Source de données est une base Mysql
  • /
class CMysql extends CDataSource { protected $result; protected $db; /**
  • Vérification de la configuration
  • /
protected function checkConfiguration() { if (!isset($this->_aConfig['HOSTNAME'])) return false; if(!isset($this->_aConfig['USER'])) return false; if(!isset($this->_aConfig['PASS'])) return false; if (!isset($this->_aConfig['DB'])) return false; return true; } /**
  • Constructeur
  • /
protected function __construct ($hostname='localhost',$user,$pass,$db) { $this->_aConfig['HOSTNAME'] = $hostname; $this->_aConfig['USER']=$user; $this->_aConfig['PASS']=$pass; $this->_aConfig['DB']=$db; $this->_bConnected=false; } /**
  • requête générique
  • /
public function query($queryString, $desc=NULL,$error_level=ERROR::WARNING) { if ($this->_BENCH) $start = microtime (true); $this->result = @mysql_query ($queryString, $this->_ressource ); if ($this->_BENCH) $query_time = microtime (true) - $start; else $query_time=null; if ($this->result) { $this->history[++$this->query_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time); return $this->result; } else { ERROR::procError($error_level,get_class(),'::'.__FUNCTION__.': '.mysql_error().' in '.$queryString.' at line '.__LINE__ ); if (count($this->history)>0) $this->result = @mysql_query ($this->history[$this->query_id]['query'], $this->_ressource ); $this->errHistory[++$this->errQuery_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time, 'queryPos'=>$this->query_id+1); return false; } } /**
  • verification de la configuration + connexion
  • /
protected function connect() { if ( !$this->checkConfiguration() ) { ERROR::procError(ERROR::FATAL,get_class(),''.__FUNCTION__.':Problème de configuration de connexion détecté.'); } $this->_ressource = @mysql_connect($this->_aConfig['HOSTNAME'], $this->_aConfig['USER'], $this->_aConfig['PASS']); if (!$this->_ressource ) { ERROR::procError(ERROR::FATAL,get_class(),'::'.__FUNCTION__.':Erreur lors de la connection vers : '.$this->_aConfig['HOSTNAME'].' Veuillez vérifier les paramètres utilisés pour votre connexion.'); } $this->db=@mysql_select_db($this->_aConfig['DB'], $this->_ressource); if (!$this->db ) { ERROR::procError(ERROR::FATAL,get_class(),'::'.__FUNCTION__.':Erreur lors de l\'ouverture de la base de donnée : '.$this->_aConfig['DB'].'.Veuillez vérifier l\'existence de la base.'); unset($this->_aConfig); } $this->_bConnected=true; } /**
  • retourne le resultat de la requête sous forme de tableau
  • associatif
  • /
public function fetch_assoc($result=null) { if ($result) { return mysql_fetch_assoc ($result); } if (preg_match('/\b(?i)select/s',$this->history[$this->query_id]['query'])) { if ($this->result) { return mysql_fetch_assoc ($this->result); } } else ERROR::procError(ERROR::WARNING,get_class(),''.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } /** retourne le nombre de lignes d'une requête
  • /
public function num_rows($result=null) { if ($result) return mysql_num_rows($result); if ($this->result) { if (preg_match('/\b(?i)select/s',$this->history[$this->query_id]['query'])) return mysql_num_rows($this->result); else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } } /**
  • /
public function insert_id() { return mysql_insert_id($this->_ressource); } /**
  • /
public function error() { return mysql_error(); } /**
  • libére la resource resultat
  • /
public function free($result=null) { if ($result) { @mysql_free_result($result)or ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': problème durant la libération des ressources'); } else { if (preg_match('/\b(?i)select/s',$this->history[$this->query_id]['query'])) { $free = @mysql_free_result($this->result); if (!$free) ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': problème durant la libération des ressources'); } else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } } } class CSqlite extends CDataSource { /**
  • Constructeur
  • /
protected function __construct ($dbfilename='db_xxx',$mode=0666) { $this->_aConfig['DBFILENAME'] = $dbfilename; $this->_aConfig['MODE']=$mode; $this->_aConfig['SQLITEERROR']=null; $this->_bConnected=false; } protected function checkConfiguration() { if (!isset($this->_aConfig['DBFILENAME'])) return false; if(!isset($this->_aConfig['MODE'])) return false; return true; } protected function connect() { if ( !$this->checkConfiguration() ) { ERROR::procError(ERROR::FATAL,get_class(),''.__FUNCTION__.':Problème de configuration de connexion détecté.'); } $this->_ressource = @sqlite_open($this->_aConfig['DBFILENAME'], $this->_aConfig['MODE'], $this->_aConfig['SQLITEERROR']); if (!$this->_ressource ) { ERROR::procError(ERROR::FATAL,get_class(),'::'.__FUNCTION__.':Erreur lors de la connection : '.$sqliteError.'.'); } $this->_bConnected=true; } /**
  • requête générique
  • /
public function query($queryString, $desc=NULL,$error_level=ERROR::WARNING) { if ($this->_BENCH) $start = microtime (true); $this->result = @sqlite_query ( $this->_ressource,$queryString ); if ($this->_BENCH) $query_time = microtime (true) - $start; else $query_time=null; if ($this->result) { $this->history[++$this->query_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time); return $this->result; } else { ERROR::procError($error_level,get_class(),'::'.__FUNCTION__.': '.$this->error().' in '.$queryString.' at line '.__LINE__ ); if (count($this->history)>0) $this->result = @sqlite_query ($this->_ressource, $this->history[$this->query_id]['query']); // Historisatoin des erreurs $this->errHistory[++$this->errQuery_id] = array('desc' => $desc, 'query' => $queryString, 'time' => $query_time, 'queryPos'=> $this->query_id+1); return false; } } /**
  • retourne le resultat de la requête sous forme de tableau
  • associatif
  • /
public function fetch_assoc($result=null) { if ($result) { return sqlite_fetch_array ($result,SQLITE_ASSOC); } if (preg_match('/\b(?i)select/s',$this->history[$this->query_id]['query'])) { if ($this->result) { return sqlite_fetch_array ($this->result,SQLITE_ASSOC); } } else ERROR::procError(ERROR::WARNING,get_class(),''.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } /**
  • /
public function insert_id() { return sqlite_last_insert_rowid($this->_ressource); } /** retourne le nombre de lignes d'une requête
  • /
public function num_rows($result=null) { if ($result) return sqlite_num_rows($result); if ($this->result) { if (preg_match('/\b(?i)select/s',$this->history[$this->query_id]['query'])) return sqlite_num_rows($this->result); else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } else ERROR::procError(ERROR::WARNING,get_class(),'::'.__FUNCTION__.': fonction incompatible avec la ressource utilisée'); } /**
  • libére la resource resultat
  • /
public function free() { unset ($this->result); } /**
  • clot la connexion
  • /
public function close(){ sqlite_close($this->_ressource); $this->_bConnected=false; } /**
  • recharge la classee à partir de la dernière configuration
  • /
public function reload(){ $this->connect(); } /**
  • /
public function error() { $errCode = sqlite_last_error($this->_ressource); return 'SQLITE - '.$errCode.':'.sqlite_error_string($errCode); } } try { /** Exemple d'utilisation de la classe CSimpleXml
  • /
echo '<br/><strong>EXEMPLE OBJET SIMPLEXML: </strong>'; //bench à true pour toutes les classes filles $oxml=CDataSource::add('CSimpleXml','test3.xml'); $oxml->setBench(true); $oxml->query('b/c','xpath'); $i=0; /** Même comportement qu'une requête fetch_assoc
  • /
while ($node=$oxml->fetch_assoc()) { $row[$i]=$node; $i++; } //erreur de requête $oxml->query('b/cd','xpath'); /** Affichage du tableau généré par fetch_assoc
  • /
echo '<br/> AFFICHAGE DU FETCH ALL POUR RESSOURCE:<br/>'; print_r ($row); /** Affichage du tableau historisation
  • /
print_r($oxml->history); echo '<br/>REQUETES OK : '; print_r($oxml->history); echo '<br/>REQUETES EN ERREUR : '; print_r($oxml->errHistory); $oxml->free(); $oxml->close(); if ($oxml->isConnected()) echo '<br/>oxml connecté'; else echo '<br/>oxml non connecté'; $oxml->reload(); if ($oxml->isConnected()) echo '<br/>oxml connecté'; else echo '<br/>oxml non connecté'; /** Exemple avec une source de données Mysql
  • /
echo '<br/><strong>EXEMPLE OBJET MYSQL: </strong>'; $oMysql = CDataSource::add('CMysql','localhost','changeusr','changepwd','param'); $oMysql->setBench(true); $oMysql->query('SELECT * from param'); $i=0; while ($row = $oMysql->fetch_assoc()) { $tab[$i]=$row; $i++; } $oMysql->free(); $oMysql->query('insert into param (pkey,pval) values (\'key6\',\'val4\')'); echo '<br/>AFFICHAGE DU FETCH ALL POUR RESSOURCE:<br/>'; print_r($tab); echo '<br/>REQUETES OK : '; print_r($oMysql->history); echo '<br/>REQUETES EN ERREUR : '; print_r($oMysql->errHistory); $oMysql->free(); /** Exemple avec une source de données sqlite
  • /
echo '<br/><strong>EXEMPLE OBJET SQLITE: </strong>'; $oDbsqlite = CDataSource::add('CSqlite','db_test', 0666); $oDbsqlite->setBench(true); $oDbsqlite->query('CREATE TABLE user ( ID_user BIGINT NOT NULL PRIMARY KEY DEFAULT \'0\', login VARCHAR(10), password VARCHAR(10))'); $oDbsqlite->query('INSERT INTO user (ID_user,login,password) VALUES (1,\'user_test\',\'pass\')'); $oDbsqlite->query('SELECT * FROM user'); $i=0; while ($row=$oDbsqlite->fetch_assoc()) { $rows[$i]=$row; $i++; } /** Autre exemple de requête au passage et pour la forme : une des dates les plus importantes de l'année ;)
  • /
$oDbsqlite->query('SELECT date( \'now\', \'start of year\', \'+10 months\', \'+14 days\', \'weekday 4\') as datebojo'); if ($row=$oDbsqlite->fetch_assoc()) { echo 'le beaujo arrive le: '.$row['datebojo'].'<br/>'; } $oDbsqlite->free(); $oDbsqlite->close(); echo '<br/> AFFICHAGE DU FETCH ALL POUR RESSOURCE:<br/>'; print_r($rows); echo '<br/>REQUETES OK : '; print_r($oDbsqlite->history); echo '<br/>REQUETES EN ERREUR : '; print_r($oDbsqlite->errHistory); } catch(Exception $excep) { echo $excep->getMessage(); } ?>

Conclusion :


Merci de signaler d'éventuels bugs.
Pour exécuter ce script il est nécessaire de paramètrer les extensions pdo,sqlite,mysql et simpleXML dans php.ini.
sinon les parties d'appel aux extensions non chargées sont à mettre en commentaires ou à supprimer

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.