NxTIC/build/lightdb/core/lightdb.php

436 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace lightdb\core;
class lightdb{
// REPERTOIRE RACINE DE TOUTES LES BDD
public static function default_root(){ return __BUILD__.'/lightdb/storage/'; }
// ATTRIBUTS
private $root;
private $dbname;
private $dir;
private $index;
private $driver;
private $line;
private $tmp;
/* CONSTRUCTEUR -> CREER LES FICHIERS S'ILS N'EXISTENT PAS SINON, RECUPERE LES DONNES
*
* @dbname<String> 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) ) return;
$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; }
/* RETOURNE LA LISTE DES INDEX
*
* @i<String> Index pour lequel on veut la ligne et le hash
*
* @return Index<Array> Tableau associatif contenant le hash et la ligne
*
*/
public function index($i=null){
return is_numeric($i) ? $this->index : $this->index;
}
/* INSERTION D'UNE ENTREE DANS LA BASE DE DONNEES
*
* @key<String> Clé qui permettra l'accès direct
* @data<mixed*> Objet qui sera enregistré dans la base
*
* @return status<Boolean> 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<Array> Tableau de 'clés'->'valeurs' à insérer
* @data<mixed*> Objet qui sera enregistré dans la base
*
* @return status<Boolean> 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<String> Clé associée à la valeur à récupérer
*
* @return data<mixed*> 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<Array> Clés associées aux valeurs à récupérer
*
* @return data<mixed*> 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<String> Clé de l'entrée à supprimer
*
* @return status<Boolean> 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 = __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<Array> Clés des entrées à supprimer
*
* @return status<Boolean> 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 = __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<typeParam> Description du param
*
* @return nomRetour<typeRetour> 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; }
}