CREER LES FICHIERS S'ILS N'EXISTENT PAS SINON, RECUPERE LES DONNES * * @dbname Nom de la base de données * */ public function __construct($dbname, $root=null){ /* [0] On récupère les attributs =========================================================*/ $this->root = is_null($root) ? self::default_root().'/' : $root; $this->dbname = $dbname; $this->dir = $this->root.$dbname.'/'; /* [1] Création du répertoire s'il n'existe pas =========================================================*/ if( !is_dir($this->dir) ) mkdir($this->dir); /* [2] Création du fichier d'index ou récupération =========================================================*/ /* (1) Si le fichier n'existe pas, on le crée */ if( !file_exists($this->dir.'index') ){ $fIndex = new \SplFileObject($this->dir.'index', 'w'); $fIndex->fwrite('[]'); $fIndex = null; } /* (2) On récupère le contenu du fichier */ $fIndex = new \SplFileObject($this->dir.'index'); $fIndex->seek(0); $index = json_decode( $fIndex->fgets(), true ); // Si erreur de parsage, on retourne une erreur if( is_null($index) ) throw new \Exception("Cannot parse index"); $this->index = $index; /* [3] Initialisation du gestionnaire d'acces (SplFileObject) =========================================================*/ /* (1) Si le fichier n'existe pas, on le crée */ if( !file_exists($this->dir.'data') ) file_put_contents($this->dir.'data', '' ); /* (2) On place un 'driver' sur le fichier */ $this->driver = new \SplFileObject($this->dir.'data', 'r+'); // $this->driver->setFlags( \SplFileObject::SKIP_EMPTY ); /* (3) On récupère le nombre de lignes */ $this->line = -1; while( !$this->driver->eof() ){ $this->line++; $this->driver->fgetcsv(); } } public function close(){ $this->driver = null; } /* [x] Flush the database * =========================================================*/ public function flush(){ file_put_contents($this->dir.'index', '[]' ); file_put_contents($this->dir.'data', '' ); } /* RETOURNE LA LISTE DES INDEX * * @i Index pour lequel on veut la ligne et le hash * * @return Index Tableau associatif contenant le hash et la ligne * */ public function index($i=null){ if( is_null($i) ) return $this->index; return isset($this->index[$i]) ? $this->index[$i] : NULL; } /* INSERTION D'UNE ENTREE DANS LA BASE DE DONNEES * * @key Clé qui permettra l'accès direct * @data Objet qui sera enregistré dans la base * * @return status Retourne TRUE si tout s'est bien passé, sinon FALSE * */ public function insert($key, $data){ /* (1) On vérifie que la clé est unique */ if( array_key_exists($key, $this->index) ) return true; $key = (string) $key; /* (2) On ajoute les données aux fichier */ $json_data = json_encode($data); $this->driver->seek($this->line); $this->line++; $written = $this->driver->fwrite( $json_data.PHP_EOL ); // Si erreur d'écriture, on retourne FALSE if( is_null($written) ) return false; /* (3) On enregistre l'index */ $this->index[$key] = [ 'line' => $this->line - 1, 'hash' => sha1($json_data) ]; /* (4) On enregistre le fichier index */ $fIndex = new \SplFileObject($this->dir.'index', 'w'); $fIndex->fwrite( json_encode($this->index) ); $fIndex = null; return true; } /* INSERTION D'UNE ENTREE DANS LA BASE DE DONNEES * * @dataset Tableau de 'clés'->'valeurs' à insérer * @data Objet qui sera enregistré dans la base * * @return status Retourne TRUE si tout s'est bien passé, sinon FALSE * */ public function insertAll($dataset){ /* (1) On vérifie que la clé est unique */ foreach($dataset as $key=>$data) if( array_key_exists($key, $this->index) ) unset($dataset[$key]); /* (2) On ajoute les données aux fichier */ $this->driver->seek($this->line); foreach($dataset as $key=>$data){ $json_data = json_encode($data); $this->line++; $written = $this->driver->fwrite( $json_data.PHP_EOL ); /* (3) On enregistre les index */ $this->index[$key] = [ 'line' => $this->line - 1, 'hash' => sha1($json_data) ]; } /* (4) On enregistre le fichier index */ $fIndex = new \SplFileObject($this->dir.'index', 'w'); $fIndex->fwrite( json_encode($this->index) ); $fIndex = null; return true; } /* RENVOIE LES DONNEES ASSOCIEES A UNE CLE DONNEE * * @key Clé associée à la valeur à récupérer * * @return data Renvoie la valeur associée à la clé, FALSE si erreur * */ public function fetch($key){ /* (1) On vérifie que la clé existe bien */ if( !array_key_exists($key, $this->index) ) return false; /* (2) On récupère la ligne */ $line = $this->index[$key]['line']; /* (3) On récupère le contenu */ $this->driver->seek($line); $json = json_decode( $this->driver->current(), true ); // Si erreur de parsage if( is_null($json) ) return false; return $json; } /* RENVOIE LES DONNEES ASSOCIEES AUX CLES DONNEES * * @keys Clés associées aux valeurs à récupérer * * @return data Renvoie les valeurs associées aux clé, ou un tableau vide si erreur * */ public function fetchAll($keys){ $data = []; /* (0) Pour chaque clé */ foreach($keys as $i=>$key){ /* (1) On ne prend pas en compte les clés qui n'existent pas */ if( !array_key_exists($key, $this->index) ) continue; /* (2) On récupère la ligne */ $line = $this->index[$key]['line']; /* (3) On récupère le contenu */ $this->driver->seek($line); $json = json_decode( $this->driver->current(), true ); /* (4) Si pas d'erreur de parsage, On enregistre */ if( !is_null($json) ) $data[$key] = $json; } return $data; } /* SUPPRIME UNE ENTREE DE CLE DONNEE DE LA BASE DE DONNEES * * @key Clé de l'entrée à supprimer * * @return status Retourne TRUE si tout s'est bien passé, sinon FALSE * */ public function delete($key){ /* (1) On vérifie l'existence de la clé */ if( !array_key_exists($key, $this->index) ) return true; // On considère que l'action souhaitée est effectuée $line = $this->index[$key]['line']; /* (2) On réarrange la bd pour supprimer la ligne */ $tmpfilename = __ROOT__.'/tmp/'.uniqid().'.dat'; $tmpfile = new \SplFileObject($tmpfilename, 'w'); $this->driver->seek(0); // On recopie toutes les lignes sauf celle à supprimer dans un fichier temporaire while( $this->driver->key() < $this->line ){ if( $this->driver->key() != $line ) $tmpfile->fwrite( $this->driver->current() ); $this->driver->next(); } // On décrémente le nb de lignes $this->line--; $tmpfile = null; /* (3) On remplace le fichier original par le fichier temporaire */ $this->driver = null; rename($tmpfilename, $this->dir.'data'); $this->driver = new \SplFileObject($this->dir.'data', 'r+'); /* (3) On supprime la ligne de l'index */ unset( $this->index[$key] ); /* (4) On met à jour les index des lignes déplacées */ foreach($this->index as $i=>$indexData) if( $indexData['line'] > $line ) $this->index[$i]['line']--; // on décrémente les lignes au dessus de la ligne supprimée /* (5) On enregistre le fichier index */ $fIndex = new \SplFileObject($this->dir.'index', 'w'); $fIndex->fwrite( json_encode($this->index) ); $fIndex = null; return true; } /* SUPPRIME PLUSIEURS ENTREES DE CLES DONNEES DE LA BASE DE DONNEES * * @keys Clés des entrées à supprimer * * @return status Retourne TRUE si tout s'est bien passé, sinon FALSE * */ public function deleteAll($keys){ $keyLines = []; /* [1] On récupère la ligne associée à chaque clé =========================================================*/ foreach($keys as $k=>$key){ /* (1) Si la clé n'existe pas, on passe à la suivante */ if( !array_key_exists($key, $this->index) ) continue; /* (2) On récupère la ligne de la clé */ $keyLines[$key] = $this->index[$key]['line']; } /* [2] On trie les clés en fonction de leur ligne =========================================================*/ $sorted = []; // Tant que toute les clés ne sont pas triées while( count($keyLines) > 0 ){ // Contiendra la clé de la plus petite valeur $min = null; // On cherche la ligne la plus petite foreach($keyLines as $key=>$line) if( is_null($min) || $line < $keyLines[$min] ) // Si valeur inf à min $min = $key; // On ajoute la plus petite clé trouvée a la liste $sorted[$min] = $keyLines[$min]; // On la supprime du tableau à trier unset($keyLines[$min]); } /* [3] On supprime les lignes à supprimer =========================================================*/ /* (1) On réarrange la bd pour supprimer la ligne */ $tmpfilename = __ROOT__.'/tmp/'.uniqid().'.dat'; $tmpfile = new \SplFileObject($tmpfilename, 'w'); $this->driver->seek(0); /* (2) On recopie toutes les lignes sauf celles à supprimer dans un fichier temporaire */ while( $this->driver->key() < $this->line ){ // Si la ligne en cours n'est pas dans la liste des lignes à supprimer if( !in_array($this->driver->key(), $sorted) ) $tmpfile->fwrite( $this->driver->current() ); // On l'écrit dans le nouveau fichier $this->driver->next(); } $tmpfile = null; /* (3) On remplace le fichier original par le fichier temporaire */ $this->driver = null; rename($tmpfilename, $this->dir.'data'); $this->driver = new \SplFileObject($this->dir.'data', 'r+'); /* [4] On met à jour les index =========================================================*/ $step = 0; foreach($sorted as $key=>$line){ /* (1) On décrémente le nb de lignes */ $this->line--; /* (2) On supprime la ligne de l'index */ unset( $this->index[$key] ); /* (3) On met à jour les index des lignes déplacées du nombre d'index qu'on a supprimé */ foreach($this->index as $i=>$indexData) if( $indexData['line'] > $line-$step ) $this->index[$i]['line']--; // on décrémente les lignes au dessus de la ligne supprimée $step++; } /* (4) On enregistre le fichier index */ $fIndex = new \SplFileObject($this->dir.'index', 'w'); $fIndex->fwrite( json_encode($this->index) ); $fIndex = null; return true; } /* RENVOIE LES DONNEES ASSOCIEES A UN CHAMP DE RECHERCHE * * @nomParam Description du param * * @return nomRetour Description du retour * */ public function filter($data){ /* (1) Si @data est un tableau associatif */ if( is_array($data) ){ $filtered = []; foreach($this->index as $i=>$indexData){ $this->driver->seek( $indexData['line'] ); $dbData = json_decode( $this->driver->fgets(), true ); foreach($data as $key=>$value) if( isset($dbData[$key]) && preg_match("#$value#", $dbData[$key]) ){ $filtered[$i] = $dbData; break; } } return $filtered; /* (2) Sinon on compare @data en tant que valeur simple */ }else{ $this->tmp = sha1( json_encode($data) ); return array_filter($this->index, [$this, 'simpleFilter']); } } protected function simpleFilter($e){ return $e['hash'] == $this->tmp; } }