|
Trouver une ressource
Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !
DATASOURCE : ABSTRACTION DE DONNÉES
Information sur la source
Description
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
- <!--
- 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();
- }
-
-
- ?>
-
<!--
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
Historique
- 10 décembre 2006 06:12:23 :
- ..
- 11 décembre 2006 20:33:48 :
- $tye par $type dans ERROR::procError
- 28 décembre 2006 20:01:35 :
- J'ai modifié la methode add pour ne permettre que l'instanciation d'objets appartenant aux sous-classes de datasources.
L'ancienne methode s'assimilait à une methode d'usinage d'objets de n'importe quelle classe: la construction s'inclut maintenant dans un contexte plus spécialisé.
- 30 décembre 2006 22:38:26 :
- Amélioration de la méthode fetch_assoc pour CSimpleXml
Le comportement est similaire à fecth_assoc de mysql et le format de sortie n'est plus
l'iterateur de SimpleXML mais un véritable array.
- 29 avril 2007 16:56:23 :
- Ajout de la gestion base sqlite.
- 29 avril 2007 21:10:32 :
- une modif de la description + changement du niveau de la source en initié
- 29 avril 2007 23:20:52 :
- ajout histo des erreur et meilleur gestion des erreurs pour xml
Sources du même auteur
Sources de la même categorie
Sources en rapport avec celle ci
|