ajout sqlite
This commit is contained in:
		
							parent
							
								
									3550730a9f
								
							
						
					
					
						commit
						3bd1031dda
					
				
							
								
								
									
										178
									
								
								src/db/sqlite/Sqlite.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/db/sqlite/Sqlite.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | |||||||
|  | <?php | ||||||
|  | namespace nur\sery\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use nur\b\IllegalAccessException; | ||||||
|  | use nur\sery\cl; | ||||||
|  | use SQLite3; | ||||||
|  | use SQLite3Result; | ||||||
|  | use SQLite3Stmt; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Sqlite: frontend vers une base de données sqlite3 | ||||||
|  |  */ | ||||||
|  | class Sqlite { | ||||||
|  |   static function config_enableExceptions(self $sqlite) { | ||||||
|  |     $sqlite->db->enableExceptions(true); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function verifix_query(&$query, ?array &$params=null): void { | ||||||
|  |     if (is_array($query)) { | ||||||
|  |       throw IllegalAccessException::not_implemented(); #XXX
 | ||||||
|  |     } elseif (!is_string($query)) { | ||||||
|  |       $query = strval($query); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const CONFIG = [ | ||||||
|  |     [self::class, "config_enableExceptions"], | ||||||
|  |   ]; | ||||||
|  |   const MIGRATE = null; | ||||||
|  | 
 | ||||||
|  |   const SCHEMA = [ | ||||||
|  |     "file" => ["string", ""], | ||||||
|  |     "flags" => ["int", SQLITE3_OPEN_READWRITE + SQLITE3_OPEN_CREATE], | ||||||
|  |     "encryption_key" => ["string", ""], | ||||||
|  |     "config" => ["?array|callable"], | ||||||
|  |     "migrate" => ["?array|string|callable"], | ||||||
|  |     "auto_open" => ["bool", true], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function __construct(?string $file=null, ?array $params=null) { | ||||||
|  |     if ($file !== null) $params["file"] = $file; | ||||||
|  |     ##schéma
 | ||||||
|  |     $default_file = self::SCHEMA["file"][1]; | ||||||
|  |     $this->file = strval($params["file"] ?? $default_file); | ||||||
|  |     #
 | ||||||
|  |     $default_flags = self::SCHEMA["flags"][1]; | ||||||
|  |     $this->flags = intval($params["flags"] ?? $default_flags); | ||||||
|  |     #
 | ||||||
|  |     $default_encryptionKey = self::SCHEMA["encryption_key"][1]; | ||||||
|  |     $this->encryptionKey = strval($params["encryption_key"] ?? $default_encryptionKey); | ||||||
|  |     # configuration
 | ||||||
|  |     $this->config = $params["config"] ?? static::CONFIG; | ||||||
|  |     # migrations
 | ||||||
|  |     $this->migration = $params["migrate"] ?? static::MIGRATE; | ||||||
|  |     #
 | ||||||
|  |     $default_autoOpen = self::SCHEMA["auto_open"][1]; | ||||||
|  |     if ($params["auto_open"] ?? $default_autoOpen) { | ||||||
|  |       $this->open(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** @var string */ | ||||||
|  |   protected $file; | ||||||
|  |   /** @var int */ | ||||||
|  |   protected $flags; | ||||||
|  |   /** @var string */ | ||||||
|  |   protected $encryptionKey; | ||||||
|  |   /** @var array|string|callable */ | ||||||
|  |   protected $config; | ||||||
|  |   /** @var array|string|callable */ | ||||||
|  |   protected $migration; | ||||||
|  | 
 | ||||||
|  |   /** @var SQLite3 */ | ||||||
|  |   protected $db; | ||||||
|  | 
 | ||||||
|  |   function open(): self { | ||||||
|  |     if ($this->db === null) { | ||||||
|  |       $this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey); | ||||||
|  |       SqliteConfig::with($this->config)->run($this); | ||||||
|  |       SqliteMigration::with($this->migration)->run($this); | ||||||
|  |     } | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function close(): void { | ||||||
|  |     if ($this->db !== null) { | ||||||
|  |       $this->db->close(); | ||||||
|  |       $this->db = null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function exec($query, ?array $params=null): bool { | ||||||
|  |     $this->open(); | ||||||
|  |     self::verifix_query($query, $params); | ||||||
|  |     $db = $this->db; | ||||||
|  |     if ($params === null) { | ||||||
|  |       return $db->exec($query); | ||||||
|  |     } else { | ||||||
|  |       /** @var SQLite3Stmt $stmt */ | ||||||
|  |       $stmt = SqliteException::check($db, $db->prepare($query)); | ||||||
|  |       try { | ||||||
|  |         foreach ($params as $param => $value) { | ||||||
|  |           SqliteException::check($db, $stmt->bindValue($param, $value)); | ||||||
|  |         } | ||||||
|  |         /** @var SQLite3Result $result */ | ||||||
|  |         $result = SqliteException::check($db, $stmt->execute()); | ||||||
|  |         return $result->finalize(); | ||||||
|  |       } finally { | ||||||
|  |         $stmt->close(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function get($query, ?array $params=null, bool $entireRow=false) { | ||||||
|  |     $this->open(); | ||||||
|  |     self::verifix_query($query, $params); | ||||||
|  |     $db = $this->db; | ||||||
|  |     if ($params === null) { | ||||||
|  |       return $db->querySingle($query, $entireRow); | ||||||
|  |     } else { | ||||||
|  |       /** @var SQLite3Stmt $stmt */ | ||||||
|  |       $stmt = SqliteException::check($db, $db->prepare($query)); | ||||||
|  |       try { | ||||||
|  |         foreach ($params as $param => $value) { | ||||||
|  |           SqliteException::check($db, $stmt->bindValue($param, $value)); | ||||||
|  |         } | ||||||
|  |         /** @var SQLite3Result $result */ | ||||||
|  |         $result = SqliteException::check($db, $stmt->execute()); | ||||||
|  |         try { | ||||||
|  |           $row = $result->fetchArray(SQLITE3_ASSOC); | ||||||
|  |           if ($row === false) return null; | ||||||
|  |           elseif ($entireRow) return $row; | ||||||
|  |           else return cl::first($row); | ||||||
|  |         } finally { | ||||||
|  |           $result->finalize(); | ||||||
|  |         } | ||||||
|  |       } finally { | ||||||
|  |         $stmt->close(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function all($query, ?array $params=null): iterable { | ||||||
|  |     $this->open(); | ||||||
|  |     self::verifix_query($query, $params); | ||||||
|  |     $db = $this->db; | ||||||
|  |     if ($params === null) { | ||||||
|  |       /** @var SQLite3Result $result */ | ||||||
|  |       $result = SqliteException::check($db, $db->query($query)); | ||||||
|  |       try { | ||||||
|  |         while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { | ||||||
|  |           yield $row; | ||||||
|  |         } | ||||||
|  |       } finally { | ||||||
|  |         $result->finalize(); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       /** @var SQLite3Stmt $stmt */ | ||||||
|  |       $stmt = SqliteException::check($db, $db->prepare($query)); | ||||||
|  |       try { | ||||||
|  |         foreach ($params as $param => $value) { | ||||||
|  |           SqliteException::check($db, $stmt->bindValue($param, $value)); | ||||||
|  |         } | ||||||
|  |         /** @var SQLite3Result $result */ | ||||||
|  |         $result = SqliteException::check($db, $stmt->execute()); | ||||||
|  |         try { | ||||||
|  |           while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { | ||||||
|  |             yield $row; | ||||||
|  |           } | ||||||
|  |         } finally { | ||||||
|  |           $result->finalize(); | ||||||
|  |         } | ||||||
|  |       } finally { | ||||||
|  |         $stmt->close(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								src/db/sqlite/SqliteConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/db/sqlite/SqliteConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  | namespace nur\sery\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use nur\sery\php\func; | ||||||
|  | 
 | ||||||
|  | class SqliteConfig { | ||||||
|  |   static function with($configs): SqliteConfig { | ||||||
|  |     if ($configs instanceof self) return $configs; | ||||||
|  |     return new static($configs); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const CONFIG = null; | ||||||
|  | 
 | ||||||
|  |   function __construct($configs) { | ||||||
|  |     if ($configs === null) $configs = static::CONFIG; | ||||||
|  |     if ($configs === null) $configs = []; | ||||||
|  |     elseif (is_string($configs)) $configs = [$configs]; | ||||||
|  |     elseif (is_callable($configs)) $configs = [$configs]; | ||||||
|  |     elseif (!is_array($configs)) $configs = [strval($configs)]; | ||||||
|  |     $this->configs = $configs; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** @var array */ | ||||||
|  |   protected $configs; | ||||||
|  | 
 | ||||||
|  |   function run(Sqlite $sqlite): void { | ||||||
|  |     foreach ($this->configs as $key => $config) { | ||||||
|  |       if (is_callable($config)) { | ||||||
|  |         func::call($config, $sqlite, $key, $this); | ||||||
|  |       } else { | ||||||
|  |         $sqlite->exec($config); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/db/sqlite/SqliteException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/db/sqlite/SqliteException.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  | namespace nur\sery\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use RuntimeException; | ||||||
|  | use SQLite3; | ||||||
|  | 
 | ||||||
|  | class SqliteException extends RuntimeException { | ||||||
|  |   static final function check(SQLite3 $db, $value, bool $throw=true) { | ||||||
|  |     if ($value !== false) return $value; | ||||||
|  |     elseif (!$throw) return null; | ||||||
|  |     else throw new static($db->lastErrorMsg(), $db->lastErrorCode()); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								src/db/sqlite/SqliteMigration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/db/sqlite/SqliteMigration.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | <?php | ||||||
|  | namespace nur\sery\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use nur\sery\php\func; | ||||||
|  | 
 | ||||||
|  | class SqliteMigration { | ||||||
|  |   static function with($migrations): SqliteMigration { | ||||||
|  |     if ($migrations instanceof self) return $migrations; | ||||||
|  |     return new static($migrations); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const MIGRATE = null; | ||||||
|  | 
 | ||||||
|  |   function __construct($migrations) { | ||||||
|  |     if ($migrations === null) $migrations = static::MIGRATE; | ||||||
|  |     if ($migrations === null) $migrations = []; | ||||||
|  |     elseif (is_string($migrations)) $migrations = [$migrations]; | ||||||
|  |     elseif (is_callable($migrations)) $migrations = [$migrations]; | ||||||
|  |     elseif (!is_array($migrations)) $migrations = [strval($migrations)]; | ||||||
|  |     $this->migrations = $migrations; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** @var callable[]|string[]  */ | ||||||
|  |   protected $migrations; | ||||||
|  |    | ||||||
|  |   function run(Sqlite $sqlite): void { | ||||||
|  |     $sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)"); | ||||||
|  |     foreach ($this->migrations as $key => $migration) { | ||||||
|  |       $exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [ | ||||||
|  |         "key" => $key, | ||||||
|  |       ]); | ||||||
|  |       if (!$exists) { | ||||||
|  |         $sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [ | ||||||
|  |           "key" => $key, | ||||||
|  |           "value" => $migration, | ||||||
|  |           "done" => 0, | ||||||
|  |         ]); | ||||||
|  |         if (is_callable($migration)) { | ||||||
|  |           func::call($migration, $sqlite, $key, $this); | ||||||
|  |         } else { | ||||||
|  |           $sqlite->exec($migration); | ||||||
|  |         } | ||||||
|  |         $sqlite->exec("update _migration set done = 1 where key = :key", [ | ||||||
|  |           "key" => $key, | ||||||
|  |         ]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								tests/db/sqlite/SqliteTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/db/sqlite/SqliteTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  | namespace nur\sery\db\sqlite; | ||||||
|  | 
 | ||||||
|  | use PHPUnit\Framework\TestCase; | ||||||
|  | 
 | ||||||
|  | class SqliteTest extends TestCase { | ||||||
|  |   const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)"; | ||||||
|  |   const INSERT_JEPHTE = "insert into person(nom, prenom, age) values ('clain', 'jephte', 50)"; | ||||||
|  |   const INSERT_JEAN = "insert into person(nom, prenom, age) values ('payet', 'jean', 32)"; | ||||||
|  | 
 | ||||||
|  |   function testMigration() { | ||||||
|  |     $sqlite = new Sqlite(":memory:", [ | ||||||
|  |       "migrate" => [ | ||||||
|  |         self::CREATE_PERSON, | ||||||
|  |         self::INSERT_JEPHTE, | ||||||
|  |       ], | ||||||
|  |     ]); | ||||||
|  |     self::assertSame("clain", $sqlite->get("select nom, age from person")); | ||||||
|  |     self::assertSame([ | ||||||
|  |       "nom" => "clain", | ||||||
|  |       "age" => 50, | ||||||
|  |     ], $sqlite->get("select nom, age from person", null, true)); | ||||||
|  | 
 | ||||||
|  |     $sqlite->exec(self::INSERT_JEAN); | ||||||
|  |     self::assertSame("payet", $sqlite->get("select nom, age from person where nom = 'payet'")); | ||||||
|  |     self::assertSame([ | ||||||
|  |       "nom" => "payet", | ||||||
|  |       "age" => 32, | ||||||
|  |     ], $sqlite->get("select nom, age from person where nom = 'payet'", null, true)); | ||||||
|  | 
 | ||||||
|  |     self::assertSame([ | ||||||
|  |       ["key" => "0", "value" => self::CREATE_PERSON, "done" => 1], | ||||||
|  |       ["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1], | ||||||
|  |     ], iterator_to_array($sqlite->all("select key, value, done from _migration"))); | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user