Page 1 sur 1

Classe de gestion de données dans fichiers textes

Posté : 22 févr. 2007, 12:13
par jojolapine
Bonjour à tous,
Je viens aujourd'hui proposer ma réalisation (la première version potable ;-) )...
Alors je présente le basard, c'est une classe permettant de gérer des données à la manière de sgdb actuels...
C'est à dire que les champ peuvent avoir des types (int,string,time -bientôt-), des valeurs par défaut sont possibles, un champ auto_increment est possible également, une option null/not_null est possible également... bref le basique...
Seuelement je vous arrête tout de suite, les possibilitée s'arrête là, en effet il n'est pas prévu de faire fonctionner le tout pour structure de données compliquée c'est à dire avec relation entre bases....
Cet outil peut donc servir pour la gestion de logs sur un site, ou encore pour un calendrier "simple", une liste de taches etc...

bref, voici la classe:
<?php
/**
 * Classe dataTxt
 * Permet de gérer des données simplement
 * Dans des fichiers textes
 *
 * Attention, cette classe ne se veux pas une alternative
 * aux sgbd actuels (mysql,pgsql,...)
 * Car les actions possible sont restreintes...
 * En effet, l'optique est "mono-base", c'est à dire
 * pas de structure incluant plusieurs base de données
 * et des liens entre elles...
 * 
 * Exemple d'utilisation :
 * <code><?php
 *
 * //création d'une base de donnée
 *
 * include('dataTxt.classe.php');
 * $data=new dataTxt();
 *
 * $data->newDataFile(
 *              'bdd.txt',
 *              array(
 *                  array(
 *                      'col_name'=>'id',
 *                      'col_type'=>'int',
 *                      'col_default_value'=>null,
 *                      'col_notnull'=>true,
 *                      'col_autoinc'=>true
 *                   ),
 *                  array(
 *                      'col_name'=>'nom',
 *                      'col_type'=>'string',
 *                      'col_default_value'=>'dup#ond',
 *                      'col_notnull'=>true,
 *                      'col_autoinc'=>false
 *                  ),
 *                  array(
 *                      'col_name'=>'prenom',
 *                      'col_type'=>'string',
 *                      'col_default_value'=>'jean',
 *                      'col_notnull'=>true,
 *                      'col_autoinc'=>false
 *                  ),
 *              )
 *          );
 * ?></code>
 * <code><?php
 *
 * //connection à une bdd
 * //actions diverses
 *
 * include('dataTxt.classe.php');
 * $data=new dataTxt('bdd.txt');
 *
 *
 * //insertion de données
 * if($data->insert(array('id'=>null,'nom'=>'dupond','prenom'=>'paul')))
 *      echo "Enregistrement effectué";
 * else
 *      echo "Erreur à l'enregistrement";
 *
 *
 * //récupération de données
 *
 * $results=$data->select(array('nom','prenom'),'all');
 * echo '<pre>';
 * print_r($res);
 * echo '</pre>';
 * ?></code>
 * 
 * @author Joris Mulliez <[email protected]>
 * @version 0.0.2
 * @since PHP5.x.x 
 * @package dataTxt
 */ 
class dataTxt {

    /**
    *  chemin vers le fichier de données
    */     
    private $file=null;
  
    /**
    *  séparateur de champs
    */     
    private $separator="#";
    
    /**
    *  séparateur de champs
    */     
    private $separatorProtected="#";
  
    /**
    *  séparateur entre le nom et le type d'un champ
    */     
    private $typeSeparator="|";
    
    /**
    *  séparateur entre le nom et le type d'un champ
    */     
    private $typeSeparatorProtected="\|";
  
    /**
    *  structure du fichier
    */     
    private $structure=null;

    /**
    *  Pas de gestion des erreurs
    */     
    const NO_ERROR=0;
  
    /**
    *  Gestion des erreurs haute
    */     
    const ALL_ERROR=1;

    /**
    *  Contient le niveau de gestion d'erreurs
    */     
    private $errorLevel=ALL_ERROR;
  
    /**
    * dataTxt::__construct.
    * 
    * Méthode qui initialise les paramètres: le fichier sur lequel on travail
    * Et la gestion des erreurs
    *
    * Les paramètres sont facultatifs
    * @access public
    * @param   string    $file     chemin du fichier de données
    * @param   string    $errolevel  Type of source : NULL, FILE, CURL.
    * @return   bool 
    */ 
    public function __construct($file=false,$errorLevel=null){
        if($errorLevel!==null)
            $this->errorLevel=$errorLevel;
        if($file!==false)
            return $this->selectFile($file);
        return true;
    }
  
    
  
    /**
    * dataTxt::insert.
    * 
    * Méthode d'insertion de données dans le fichier texte
    *
    * Le paramètres à entrer est un tableau de cette forme
    *   array(
    *       'colonne1'=> null,
    *       'colonne2'=>'value2',
    *       'colonne3'=>'value3'
    *   )
    *
    * @access public
    * @param   array   $donnees     données à insérer
    * @return   bool 
    */ 
    public function insert($donnees){
        
        if($this->structure==null)
            $this->getStructure();
            
        if(@$fp=fopen($this->file,'a')){
            $content='';
            $i=0;
            foreach($this->structure as $key=>$value){
                if($key==='autoinc') 
                    continue;
                if($i>0) 
                    $content.=$this->separator;
                if(!empty($donnees[$value['col_name']])){
                    $content.=$this->escape($donnees[$value['col_name']]);
                } else {
                    if($value['col_autoinc']){
                        $this->structure['autoinc']=$this->structure['autoinc']+1;
                        $content.=$this->structure['autoinc'];
                    } elseif($value['col_notnull'] && !empty($value['col_default_value'])){
                        $content.=$value['col_default_value'];
                    } else {
                        $this->error("Une valeur doit être spécifiée pour la colonne <b>".$value['col_name']."</b>");
                    }
                }
                $i++;
            }

            if(@fwrite($fp,"\r\n".$content)===false){
                if($this->errorLevel==self::ALL_ERROR){
                    $this->error("Impossible d'&eacute;crire dans le fichier ".$this->file);
                } else {
                    return false;
                }
            } else {
                fclose($fp);
                $this->change_id();
                return true;
            }
        } else {
            if($this->errorLevel==self::ALL_ERROR){
                $this->error("Impossible d'ouvrir le fichier ".$this->file);
            } else {
                return false;
            }
        }
    }
    
    
  
    /**
    * dataTxt::newDataFile
    * 
    * Méthode de création de fichier de donnée
    *
    * Le paramètres $structure est un tableau de cette forme
    *   array(
    *       array(
    *           'col_name'=>'name',
    *           'col_type'=>'type', // int|string|time
    *           'col_default_value'=>'default_value',  // string|null
    *           'col_notnull'=>true or false,
    *           'col_autoinc'=>true or false
    *       ),
    *       etc...
    *   )
    *
    * @access public
    * @param   string   $filename     chemin du fichier à créer
    * @param   array   $structure     tableau de structure
    * @return   bool 
    */ 
    public function newDataFile($filename,$structure){

        $structure=$this->escape_array($structure);
        $flag_autoinc=0;

        $content="##Struct##";
        $temp=array();
        for($i=0;$i<count($structure);$i++){
            $temp[$i]=$structure[$i]['col_name'].$this->typeSeparator;
            $temp[$i].=$structure[$i]['col_type'].$this->typeSeparator;
            $temp[$i].=$structure[$i]['col_default_value'].$this->typeSeparator;
            $temp[$i].=($structure[$i]['col_notnull'] ? '1' :'0').$this->typeSeparator;
            if($structure[$i]['col_autoinc']){
                $temp[$i].='1';
                $flag_autoinc++;
                if($flag_autoinc>1)
                    $this->error("Un seul champ de type auto increment est autorisé");
            } else {
                $temp[$i].='0';
            }
		}
            $content.=implode($this->separator,$temp);
            $content.='##autoInc##'.($flag_autoinc==1 ? '0' : 'false');
            echo $content.'<br />';

            if(@$fp=fopen($filename,'w')){
                if(@fwrite($fp,$content)===false){
                    fclose($fp);
                    if($this->errorLevel==self::ALL_ERROR){
                        $this->error("Impossible d'&eacute;crire dans le fichier ".$filename);
                    } else {
                        return false;
                    }
                } else {
                    $this->selectFile($filename);
                    return true;
                }
            } else {
                if($this->errorLevel==self::ALL_ERROR){
                    $this->error("Impossible de créer le fichier ".$filename);
                } else {
                    return false;
                }
            }
        }

    /**
    * dataTxt::select
    * 
    * Méthode de selection de données dans le fichier texte
    *
    * @access public
    * @param   mixed   $what     ce qu'on veut récupérer
    * @param   mixed   $where    filtre de selection
    * @return   array 
    */ 
    public function select($what='all',$where='all'){
        if($this->structure==null) $this->getStructure();
        $lines=file($this->file);
        $result=array();
        for($i=1;$i<count($lines);$i++){
            if($where!=='all' && is_array($where)){
                foreach($where as $key=>$value){
                    $tab=$this->explode_reg($this->separator,$lines[$i]);
                    $j=0;
                    foreach($this->structure as $key2=>$value2){
                        if($key2!=='autoinc')
                            $tab[$value2['col_name']]=$tab[$j++];
                    }
                    if(is_array($value)){
                        foreach($value as $bleuh){
                            if(isset($tab[$key]) && $tab[$key]==$bleuh){
                                if($what!=='all' && is_array($what)){
                                    foreach($tab as $keypsi=>$psi){
                                        if(in_array($keypsi,$what,true)!==false){
                                            $temp[$keypsi]=$psi;
                                        }
                                    }
                                    $result[]=$temp;
                                } else {
                                    $result[]=$tab;
                                }
                            }
                        }
                    } else {
                        if(isset($tab[$key]) && $tab[$key]==$value)
                            $result[]=$tab;
                    }
                }
            } else {
                $tab=$this->explode_reg($this->separator,$lines[$i]);
                $j=0;
                foreach($this->structure as $key2=>$value2){
                    if($key2!=='autoinc'){
                        $tab[$value2['col_name']]=$this->unescape($tab[$j]);
                        $j++;
                    }
                }

                if($what!=='all' && is_array($what)){
                    foreach($tab as $keypsi=>$psi){
                        if(in_array($keypsi,$what,true)!==false){
                            $temp[$keypsi]=$this->unescape($psi);
                        }
                    }
                    $result[]=$temp;
                } else {
                    $result[]=$tab;
                }
            }
        }
        return $result;
    }
  
  
    /**
    * dataTxt::delete
    * 
    * Méthode de supression de données dans le fichier texte
    *
    * @access public
    * @param   mixed   $what     ce qu'on veut supprimer
    * @return   bool 
    */ 
		public function delete($what='all'){
        if($this->structure==null) 
            $this->getStructure();
        $content='';
        $lines=file($this->file);
        if($what==='all'){
            if(file_put_contents($this->file,str_replace("\r\n",'',$lines[0]))){
                $this->change_id(0);
                return true;
            } else {
                return false;
            }
        } else {
			$k=0;
            for($i=1;$i<count($lines);$i++){
                    $j=0;
                foreach($this->structure as $key=>$value){
					$tab=$this->explode_reg($this->separator,$lines[$i]);
					if($key!=='autoinc')
						$tab2[$value['col_name']]=$tab[$j++];
					
			    }
				foreach($tab2 as $key=>$value){
					foreach($what as $key2=>$value2){
						if($key2===$key){
							if($value2!=$value){
								$temp[]=$tab2;
							}
						}
					}
				}
            }
			$content="##Struct##";

			foreach($this->structure as $key=>$value){
				if($key!=='autoinc'){
					$content.=$value['col_name'].$this->typeSeparator;
		            $content.=$value['col_type'].$this->typeSeparator;
		            $content.=$value['col_default_value'].$this->typeSeparator;
		            $content.=($value['col_notnull'] ? '1' :'0').$this->typeSeparator;
		            $content.=($value['col_autoinc'] ? '1' :'0').$this->typeSeparator;
				}
			}
			$content.="##autoInc##".$this->structure['autoinc'];
			foreach($temp as $value)
				$content.="\r\n".implode($this->separator,$value);
			if(file_put_contents($this->file,$content)){
                return true;
            } else {
                return false;
            }
        }
	}


    /**
    * dataTxt::selectFile.
    * 
    * Méthode qui vérifie si le fichier existe
    * Si oui, initialise la variable $this->file avec ce chemin
    *
    * prend en paramètres le chemin du fichier à tester
    * @access private
    * @param   string    $filename     chemin du fichier de données
    * @return   bool 
    */ 
    private function selectFile($filename){
        if(file_exists($filename)){
            $this->file=$filename;
            return true;
        } else {
            if($errorLevel==self::NO_ERROR){
                return false;
            } else {
                $this->error("Le fichier <b>".$file."</b> n'éxiste pas!");
            }
        }
    }
    
    
    
    /**
    * dataTxt::explode_reg.
    * 
    * Méthode similaire à explode, mais qui gère la protection
    * de caractères
    *
    * prend en paramètres $char (le caractère protégé) et $str
    * la chaine à éclater
    * retourne un tableau contenant les "éclats"
    *
    * @access private
    * @param   string    $char     caractère protégé
    * @param   string    $str      chaine à eclater
    * @return   array 
    */ 
    private function explode_reg($char,$str){
        $res=array();
        $j=0;
        for($i=0;$i<strlen($str);$i++){
            if($str{$i}==$char && $str{$i-1}!='\\'){
                $j++;
            }
            $res[$j].=(($str{$i}==$char && $str{$i-1}!='\\') || $str{$i}=="\n" || $str{$i}=="\r") ? '' : $str{$i};
        }
        return $res;
    }
    
    
    /**
    * dataTxt::getStructure.
    * 
    * Méthode qui initialise la variable $this->structure, en parsant
    * le fichier txt
    *
    * @access private
    * @param   none
    * @return   none 
    */ 
    private function getStructure(){
        $lines=file($this->file);
        $temp=explode('##Struct##',$lines[0]);
        $temp=explode('##autoInc##',$temp[1]);
        if(count($lines)>1){
        $autoinc=explode("\r\n",$temp[1]);
            if($autoinc=='false')
                $struct['autoinc']=false;
            else
                $struct['autoinc']=(int)$autoinc[0];
        } else {
            if($temp[1]=='false')
                $struct['autoinc']=false;
            else
                $struct['autoinc']=$temp[1];
        }

        $temp=$this->explode_reg($this->separator,$temp[0]);
        foreach($temp as $value){
            $tab=$this->explode_reg($this->typeSeparator,$value);
            $struct[]=array(
                    'col_name'=>$tab[0],
                    'col_type'=>$tab[1],
                    'col_default_value'=>$tab[2],
                    'col_notnull'=>$tab[3],
                    'col_autoinc'=>$tab[4]
            );
        }

        $this->structure=$struct;
    }
    
    
    /**
    * dataTxt::escape_array
    * 
    * Méthode de protecion des données d'un tableau
    *
    * @access private
    * @param   array   $array     données à protéger
    * @return   array 
    */ 
    private function escape_array($array){
        $temp=array();
        for($i=0;$i<count($array);$i++){
            if(is_array($array[$i])){
                foreach($array[$i] as $key=>$value){
                    $temp[$i][$key]=$this->escape($value);
                }
            }
        }
        return $temp;
    }
  
    /**
    * dataTxt::error
    * 
    * Affiche l'erreur et indique l'endroit où elle s'est produite
    *
    * @access private
    * @param   string   $message     message d'erreur
    * @return   none 
    */ 
    private function error($message){
        $errors=debug_backtrace();
        echo "<br />Erreur à la ligne <b>".$errors[1]['line']."</b> du fichier <b>".$errors[1]['file'];
        echo "</b><br />Message:<br />".$message;
        exit();
    }
  
    /**
    * dataTxt::escape
    * 
    * Méthode de protecion de donnée
    *
    * @access private
    * @param   string   $var     donnée à protéger
    * @return   string 
    */ 
    private function escape($var){
        $var=preg_replace("'([^\\\\])".$this->separatorProtected."'U","$1\\".$this->separator,$var);
        return preg_replace("'([^\\\\])".$this->typeSeparatorProtected."'U","$1\\".$this->typeSeparator,$var);
    }

    /**
    * dataTxt::escape
    * 
    * Méthode de déprotecion de donnée
    *
    * @access private
    * @param   string   $var     donnée à protéger
    * @return   string 
    */ 
    private function unescape($var){
        $var=preg_replace("'\\\\".$this->separatorProtected."'U",$this->separator,$var);
        return preg_replace("'\\\\".$this->typeSeparatorProtected."'U",$this->typeSeparator,$var);
    }


    /**
    * dataTxt::escape
    * 
    * Mise à jour de la structure du fichier pour l'id
    *
    * @access private
    * @param   none
    * @return   bool 
    */ 
    private function change_id($id=null){
        if($this->structure==null) $this->getStructure();
        if(file_exists($this->file)){
            $content=file_get_contents($this->file);
            $id=($id===null ? $this->structure['autoinc'] : $id);
            $content=preg_replace("'##autoInc##[0-9]+'","##autoInc##".$id,$content);
            echo $content;
            if(file_put_contents($this->file,$content)){
                return true;
            } else {
                $this->error("Erreur de mise à jour de l'id");
            }
        } else {
            $this->error("Erreur de mise à jour de l'id");
        }
    }


}
?>

et quelques exemples d'utilisations,
création d'un fichier de données:
<?php
include('dataTxt.classe.php');
$essai=new dataTxt();
$essai->newDataFile(
          'db.txt',
          array(
            array(
               'col_name'=>'id',
               'col_type'=>'int',
               'col_default_value'=>null,
               'col_notnull'=>true,
               'col_autoinc'=>true
            ),
            array(
               'col_name'=>'nom',
               'col_type'=>'string',
               'col_default_value'=>'dup#ond',
               'col_notnull'=>true,
               'col_autoinc'=>false
            ),
            array(
               'col_name'=>'prenom',
               'col_type'=>'string',
               'col_default_value'=>'jean',
               'col_notnull'=>true,
               'col_autoinc'=>false
            ),
          )
        );
?>
insertion/récupération de données:
<?php
include('dataTxt.classe.php');
$essai=new dataTxt('db.txt');

if($essai->insert('id'=>null,'nom'=>'mulliez','prenom'=>'joris'))
   echo 'enregistrement effectué';

$res=$essai->select(array('nom','prenom'),'all');
echo '<pre>';
print_r($res);
echo '</pre>';
?>
Voilà il reste tout de même beaucoup de choses à faire, notamment
sur la gestion d'erreur, peut-être lever des exeptions plutôt que planter le programme...
Ensuite, il faut que je fasse toute la vérifications des paramètres passés... (si les tableaux ont la bonne structure, etc)
et bien d'autres choses ;)
s'ajoute à celà ce que vous allez me proposer ci-dessous :!: :D
voili voilou,
j'attend vos remarques!

Posté : 28 févr. 2007, 14:16
par jojolapine
mise à jour de la classe dans sa version 0.0.2!
Principale amélioration, intégration d'une méthode dataTxt::delete permettant de supprimer des données... et quelques bugs corrigé...
J'en profite pour passer un petit appel au secours....
Je vous serai reconnaissant, si vous pouviez m'aider un petit peu sur l'utilisation de la classe...
En gros, est-ce que l'utilisation de tableau en arguments des fonctions, pour des like et des where vous parait appropriée, ou devrais-je essayer de me coltiner un petit language sql... (arg :? )
Et pour les plus courageux d'entres vous, si certain on un peu de temps à perdre, et qu'il remarques des bugs, je veux bien les voirs apparaitre içi ;-)
Voilà merci d'avance!

edit: je n'ai pas encore remis les exemples à jour, pour delete... j'ai pas trop le temps là, je verrai ça dans la semaine...

Posté : 01 mars 2007, 13:34
par Genova
Salut, j'aimerai savoir l'utilité de cette classe sachant que PHP intègre de base SQLite, qui permet bien plus de choses de façon plus rapides ?

Posté : 01 mars 2007, 13:40
par jojolapine
euh... ben je sais pô, j'ai commencé à dévelloper ça un jour où ma bdd était en rade.... et que j'avais un petit truc à faire...
Et puis, est-ce que Sqlite est activé sur tout les hébergements ?

Posté : 01 mars 2007, 13:49
par zeus
Toute contribution à son utilité, ne serait-ce que ce que le développeur à appris pendant le développement.

Ensuite, il est possible que quelqu'un ne puisse pas se servir d'autre solution et sera bien content de tomber sur cette solution.

Posté : 01 mars 2007, 19:51
par jojolapine
bonjour,
je voulais simplement montrer quelques bench de comparaisons entre mysql et ma classe:
500 itération avec dataTxt: 1172770913.92
500 itération avec mysql: 1172770933.03

500 itération avec dataTxt: 1172770991.25
500 itération avec mysql: 1172771015.22

500 itération avec dataTxt: 1172771030.72
500 itération avec mysql: 1172771057.38

500 itération avec dataTxt: 1172771070.67
500 itération avec mysql: 1172771100.3
fait avec le code suivant:
<?php

///////dataTxt

include('dataTxt.classe.php');
$essai=new dataTxt('bench.txt');

$time=microtime();

for($i=0;$i<500;$i++){
    $essai->insert(array('id'=>null,'nom'=>'nom'.$i,'prenom'=>'prenom'.$i));
}

echo "500 itération avec dataTxt: ".(time()-$time)."<br />";


/////mysql

$connec=mysql_connect('localhost','root','');
mysql_select_db('bench',$connec);
set_time_limit(30);
$time=microtime();

for($i=0;$i<500;$i++){
    mysql_query("INSERT INTO `bench` ( `id` , `nom` , `prenom` ) VALUES ( NULL , 'nom".$i."', 'prenom".$i."' )");
}

echo "500 itération avec mysql: ".(time()-$time)."<br />";
?>
la différence n'est pas flagrante, mais ma classe l'emporte toujours sur mysql, après comme je ne connais pas sqlite, je n'ai pas pu faire de bench...

Posté : 01 mars 2007, 23:25
par naholyr
Les systèmes par fichier (le tien comme SQLite) l'emportent toujours sur MySQL au niveau de l'insertion. C'est normal, au moment de l'insertion MySQL fait beaucoup d'opérations (indexation, manipulation de plusieurs fichiers, etc...) alors que ton script n'en fait qu'une.

Maintenant si on compare l'extraction des données, en particulier la recherche, les MySQL & consors s'en sortent forcément vainqueur par rapport à SQLite (et encore ce n'est pas flagrant sur des petites bases), et l'écart sera encore plus violent avec une classe (donc interprété, contrairement à SQLite qui est compilé).

Et puis en général dans une base de données, on fait plus de recherches que d'insertion (genre *beaucoup* plus).

Posté : 02 mars 2007, 13:16
par jojolapine
Bonjour,
c'est vrai que SQlite est compilé, et non pas interprétée, donc je doit être bien en dessous de ses performances, seuelement je ne pense pas que sqlite soit compilé sur tout les hébergements...
Pour ce qui est des select, je vais retourner faire des benchs entre mysql et ma classe, je vous dirais ce qu'il en est ;-)

Posté : 02 mars 2007, 17:51
par naholyr
Tout hébergeur proposant PHP5 propose SQLite.

SQLite existe également pour PHP4, mais je ne l'ai pas trouvé sur des hébergeurs mutualisés.

Posté : 02 mars 2007, 18:01
par jojolapine
:twisted: ahah je savais bien que ma classe php5 allait servir pour ceux qui ont php4... oups :oops: , pff bon ok je sort =>[]

Posté : 02 mars 2007, 18:20
par Hywan
t'as pris des vacances ? :P

Posté : 02 mars 2007, 18:24
par jojolapine
si c'est rapport à mes exams, on est vendredi soir.... et je travail pas le vendredi soir na!