intégration de nulib/cache
This commit is contained in:
		
							parent
							
								
									6cedfe9493
								
							
						
					
					
						commit
						651ba8c553
					
				| @ -48,6 +48,7 @@ | ||||
| 		} | ||||
| 	}, | ||||
| 	"bin": [ | ||||
| 		"php/bin/cachectl.php", | ||||
| 		"php/bin/dumpser.php", | ||||
| 		"php/bin/json2yml.php", | ||||
| 		"php/bin/yml2json.php", | ||||
|  | ||||
							
								
								
									
										7
									
								
								php/bin/cachectl.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/cachectl.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use cli\CachectlApp; | ||||
| 
 | ||||
| CachectlApp::run(); | ||||
							
								
								
									
										127
									
								
								php/cli/CachectlApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								php/cli/CachectlApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| <?php | ||||
| namespace cli; | ||||
| 
 | ||||
| use Exception; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\cache\CacheFile; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class CachectlApp extends Application { | ||||
|   const ACTION_READ = 10, ACTION_INFOS = 20, ACTION_CLEAN = 30; | ||||
|   const ACTION_UPDATE = 40, ACTION_UPDATE_ADD = 41, ACTION_UPDATE_SUB = 42, ACTION_UPDATE_SET = 43; | ||||
| 
 | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     "purpose" => "gestion de fichiers cache", | ||||
|     ["-r", "--read", "name" => "action", "value" => self::ACTION_READ, | ||||
|       "help" => "Afficher le contenu d'un fichier cache", | ||||
|     ], | ||||
|     ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, | ||||
|       "help" => "Afficher des informations sur le fichier cache", | ||||
|     ], | ||||
|     ["-k", "--clean", "name" => "action", "value" => self::ACTION_CLEAN, | ||||
|       "help" => "Supprimer le fichier cache s'il a expiré", | ||||
|     ], | ||||
|     ["-a", "--add-duration", "args" => 1, | ||||
|       "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_ADD], | ||||
|       "help" => "Ajouter le nombre de secondes spécifié à la durée du cache", | ||||
|     ], | ||||
|     ["-b", "--sub-duration", "args" => 1, | ||||
|       "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SUB], | ||||
|       "help" => "Enlever le nombre de secondes spécifié à la durée du cache", | ||||
|     ], | ||||
|     #XXX pas encore implémenté
 | ||||
|     //["-s", "--set-duration", "args" => 1,
 | ||||
|     //  "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SET],
 | ||||
|     //  "help" => "Mettre à jour la durée du cache à la valeur spécifiée",
 | ||||
|     //],
 | ||||
|   ]; | ||||
| 
 | ||||
|   protected $action = self::ACTION_READ; | ||||
| 
 | ||||
|   protected $updateAction, $updateDuration; | ||||
| 
 | ||||
|   function setActionUpdate(int $action, $updateDuration): void { | ||||
|     $this->action = self::ACTION_UPDATE; | ||||
|     switch ($action) { | ||||
|     case self::ACTION_UPDATE_SUB: | ||||
|       $this->updateAction = CacheFile::UPDATE_SUB; | ||||
|       break; | ||||
|     case self::ACTION_UPDATE_SET: | ||||
|       $this->updateAction = CacheFile::UPDATE_SET; | ||||
|       break; | ||||
|     case self::ACTION_UPDATE_ADD: | ||||
|       $this->updateAction = CacheFile::UPDATE_ADD; | ||||
|       break; | ||||
|     } | ||||
|     $this->updateDuration = $updateDuration; | ||||
|   } | ||||
| 
 | ||||
|   protected function findCaches(string $dir, ?array &$files): void { | ||||
|     foreach (glob("$dir/*") as $file) { | ||||
|       if (is_dir($file)) { | ||||
|         $this->findCaches($file, $files); | ||||
|       } elseif (is_file($file) && fnmatch("*.cache", $file)) { | ||||
|         $files[] = $file; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function main() { | ||||
|     $files = []; | ||||
|     foreach ($this->args as $arg) { | ||||
|       if (is_dir($arg)) { | ||||
|         $this->findCaches($arg, $files); | ||||
|       } elseif (is_file($arg)) { | ||||
|         $files[] = $arg; | ||||
|       } else { | ||||
|         msg::warning("$arg: fichier introuvable"); | ||||
|       } | ||||
|     } | ||||
|     $showSection = count($files) > 1; | ||||
|     foreach ($files as $file) { | ||||
|       switch ($this->action) { | ||||
|       case self::ACTION_READ: | ||||
|         if ($showSection) msg::section($file); | ||||
|         $cache = new CacheFile($file, null, [ | ||||
|           "readonly" => true, | ||||
|           "duration" => "INF", | ||||
|           "override_duration" => true, | ||||
|         ]); | ||||
|         yaml::dump($cache->get()); | ||||
|         break; | ||||
|       case self::ACTION_INFOS: | ||||
|         if ($showSection) msg::section($file); | ||||
|         $cache = new CacheFile($file, null, [ | ||||
|           "readonly" => true, | ||||
|         ]); | ||||
|         yaml::dump($cache->getInfos()); | ||||
|         break; | ||||
|       case self::ACTION_CLEAN: | ||||
|         msg::action(path::ppath($file)); | ||||
|         $cache = new CacheFile($file); | ||||
|         try { | ||||
|           if ($cache->deleteExpired()) msg::asuccess("fichier supprimé"); | ||||
|           else msg::adone("fichier non expiré"); | ||||
|         } catch (Exception $e) { | ||||
|           msg::afailure($e); | ||||
|         } | ||||
|         break; | ||||
|       case self::ACTION_UPDATE: | ||||
|         msg::action(path::ppath($file)); | ||||
|         $cache = new CacheFile($file); | ||||
|         try { | ||||
|           $cache->updateDuration($this->updateDuration, $this->updateAction); | ||||
|           msg::asuccess("fichier mis à jour"); | ||||
|         } catch (Exception $e) { | ||||
|           msg::afailure($e); | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         self::die("$this->action: action non implémentée"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										46
									
								
								php/src/cache/CacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								php/src/cache/CacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\php\func; | ||||
| use Traversable; | ||||
| 
 | ||||
| /** | ||||
|  * Interface CacheData: gestion d'une donnée mise en cache | ||||
|  */ | ||||
| abstract class CacheData { | ||||
|   function __construct(?string $name, $compute) { | ||||
|     $this->name = $name ?? ""; | ||||
|     $this->compute = func::withn($compute ?? static::COMPUTE); | ||||
|   } | ||||
| 
 | ||||
|   protected string $name; | ||||
| 
 | ||||
|   function getName() : string { | ||||
|     return $this->name; | ||||
|   } | ||||
| 
 | ||||
|   protected ?func $compute; | ||||
| 
 | ||||
|   /** calculer la donnée */ | ||||
|   function compute() { | ||||
|     $compute = $this->compute; | ||||
|     $data = $compute !== null? $compute->invoke(): null; | ||||
|     return $data; | ||||
|   } | ||||
| 
 | ||||
|   /** spécifier le chemin du cache à partir du fichier de base */ | ||||
|   abstract function setDatafile(?string $basefile): void; | ||||
| 
 | ||||
|   /** indiquer si le cache existe */ | ||||
|   abstract function exists(): bool; | ||||
| 
 | ||||
|   /** charger la donnée depuis le cache */ | ||||
|   abstract function load(); | ||||
| 
 | ||||
|   /** sauvegarder la donnée dans le cache et la retourner */ | ||||
|   abstract function save($data); | ||||
| 
 | ||||
|   /** supprimer le cache */ | ||||
|   abstract function delete(); | ||||
| } | ||||
							
								
								
									
										360
									
								
								php/src/cache/CacheFile.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								php/src/cache/CacheFile.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,360 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use Exception; | ||||
| use nulib\cv; | ||||
| use nulib\ext\utils; | ||||
| use nulib\file; | ||||
| use nulib\file\SharedFile; | ||||
| use nulib\os\path; | ||||
| use nulib\php\func; | ||||
| use nulib\php\time\DateTime; | ||||
| use nulib\php\time\Delay; | ||||
| use nulib\str; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| class CacheFile extends SharedFile { | ||||
|   /** @var string|int durée de vie par défaut des données mises en cache  */ | ||||
|   const DURATION = "1D"; // jusqu'au lendemain
 | ||||
| 
 | ||||
|   static function with($data, ?string $file=null): self { | ||||
|     if ($data instanceof self) return $data; | ||||
|     else return new static($file, $data); | ||||
|   } | ||||
| 
 | ||||
|   protected static function ensure_source($data, ?CacheData &$source, bool $allowArray=true): bool { | ||||
|     if ($data === null || $data instanceof CacheData) { | ||||
|       $source = $data; | ||||
|     } elseif (is_subclass_of($data, CacheData::class)) { | ||||
|       $source = new $data(); | ||||
|     } elseif (func::is_callable($data)) { | ||||
|       $source = new DataCacheData(null, $data); | ||||
|     } elseif (is_array($data) && $allowArray) { | ||||
|       return false; | ||||
|     } elseif (is_iterable($data)) { | ||||
|       $source = new DataCacheData(null, static function() use ($data) { | ||||
|         yield from $data; | ||||
|       }); | ||||
|     } else { | ||||
|       throw ValueException::invalid_type($source, CacheData::class); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   function __construct(?string $file, $data=null, ?array $params=null) { | ||||
|     $file ??= path::join(sys_get_temp_dir(), utils::uuidgen()); | ||||
|     $file = path::ensure_ext($file, cache::EXT); | ||||
|     $basefile = str::without_suffix(cache::EXT, $file); | ||||
| 
 | ||||
|     $this->initialDuration = Delay::with($params["duration"] ?? static::DURATION); | ||||
|     $this->overrideDuration = $params["override_duration"] ?? false; | ||||
|     $this->readonly = $params["readonly"] ?? false; | ||||
|     $this->cacheNull = $params["cache_null"] ?? false; | ||||
|     $data ??= $params["data"] ?? null; | ||||
|     $this->sources = null; | ||||
|     if (self::ensure_source($data, $source)) { | ||||
|       if ($source !== null) $source->setDatafile($basefile); | ||||
|       $this->sources = ["" => $source]; | ||||
|     } else { | ||||
|       $sources = []; | ||||
|       $index = 0; | ||||
|       foreach ($data as $key => $source) { | ||||
|         self::ensure_source($source, $source, false); | ||||
|         if ($source !== null) { | ||||
|           $source->setDatafile($basefile); | ||||
|           if ($key === $index) { | ||||
|             $index++; | ||||
|             $key = $source->getName(); | ||||
|           } | ||||
|         } elseif ($key === $index) { | ||||
|           $index++; | ||||
|         } | ||||
|         $sources[$key] = $source; | ||||
|       } | ||||
|       $this->sources = $sources; | ||||
|     } | ||||
|     parent::__construct($file); | ||||
|   } | ||||
| 
 | ||||
|   protected Delay $initialDuration; | ||||
| 
 | ||||
|   protected bool $overrideDuration; | ||||
| 
 | ||||
|   protected bool $readonly; | ||||
| 
 | ||||
|   protected bool $cacheNull; | ||||
| 
 | ||||
|   /** @var ?CacheData[] */ | ||||
|   protected ?array $sources; | ||||
| 
 | ||||
|   /** | ||||
|    * vérifier si le fichier est valide. s'il est invalide, il faut le recréer. | ||||
|    * | ||||
|    * on assume que le fichier existe, vu qu'il a été ouvert en c+b | ||||
|    */ | ||||
|   function isValid(): bool { | ||||
|     # considèrer que le fichier est invalide s'il est de taille nulle
 | ||||
|     return $this->getSize() > 0; | ||||
|   } | ||||
| 
 | ||||
|   protected ?DateTime $start; | ||||
| 
 | ||||
|   protected ?Delay $duration; | ||||
| 
 | ||||
|   protected $data; | ||||
| 
 | ||||
|   /** charger les données. le fichier a déjà été verrouillé en lecture */ | ||||
|   protected function loadMetadata(): void { | ||||
|     if ($this->isValid()) { | ||||
|       $this->rewind(); | ||||
|       [ | ||||
|         "start" => $start, | ||||
|         "duration" => $duration, | ||||
|         "data" => $data, | ||||
|       ] = $this->unserialize(null, false, true); | ||||
|       if ($this->overrideDuration) { | ||||
|         $duration = Delay::with($this->initialDuration, $start); | ||||
|       } | ||||
|     } else { | ||||
|       $start = null; | ||||
|       $duration = null; | ||||
|       $data = null; | ||||
|     } | ||||
|     $this->start = $start; | ||||
|     $this->duration = $duration; | ||||
|     $this->data = $data; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * tester s'il faut mettre les données à jour. le fichier a déjà été | ||||
|    * verrouillé en lecture | ||||
|    */ | ||||
|   protected function shouldUpdate(bool $noCache=false): bool { | ||||
|     if ($this->isValid()) { | ||||
|       $expired = $this->duration->isElapsed(); | ||||
|     } else { | ||||
|       $expired = false; | ||||
|       $noCache = true; | ||||
|     } | ||||
|     return $noCache || $expired; | ||||
|   } | ||||
| 
 | ||||
|   /** sauvegarder les données. le fichier a déjà été verrouillé en écriture */ | ||||
|   protected function saveMetadata(): void { | ||||
|     $this->duration ??= $this->initialDuration; | ||||
|     if ($this->start === null) { | ||||
|       $this->start = new DateTime(); | ||||
|       $this->duration = Delay::with($this->duration, $this->start); | ||||
|     } | ||||
|     $this->ftruncate(); | ||||
|     $this->serialize([ | ||||
|       "start" => $this->start, | ||||
|       "duration" => $this->duration, | ||||
|       "data" => $this->data, | ||||
|     ], false, true); | ||||
|   } | ||||
| 
 | ||||
|   protected function unlinkFiles(bool $datafilesOnly=false): void { | ||||
|     foreach ($this->sources as $source) { | ||||
|       if ($source !== null) $source->delete(); | ||||
|     } | ||||
|     if (!$datafilesOnly) @unlink($this->file); | ||||
|   } | ||||
| 
 | ||||
|   /** tester si $value peut être mis en cache */ | ||||
|   protected function shouldCache($value): bool { | ||||
|     return $this->cacheNull || $value !== null; | ||||
|   } | ||||
| 
 | ||||
|   protected ?DateTime $ostart; | ||||
| 
 | ||||
|   protected ?Delay $oduration; | ||||
| 
 | ||||
|   protected $odata; | ||||
| 
 | ||||
|   protected function beforeAction() { | ||||
|     $this->loadMetadata(); | ||||
|     $this->ostart = cv::clone($this->start); | ||||
|     $this->oduration = cv::clone($this->duration); | ||||
|     $this->odata = cv::clone($this->data); | ||||
|   } | ||||
| 
 | ||||
|   protected function afterAction() { | ||||
|     $modified = false; | ||||
|     if ($this->start != $this->ostart) $modified = true; | ||||
|     $duration = $this->duration; | ||||
|     $oduration = $this->oduration; | ||||
|     if ($duration === null || $oduration === null) $modified = true; | ||||
|     elseif ($duration->getDest() != $oduration->getDest()) $modified = true; | ||||
|     # égalité stricte uniquement pour $data et $datafiles
 | ||||
|     if ($this->data !== $this->odata) $modified = true; | ||||
|     if ($modified && !$this->readonly) { | ||||
|       $this->lockWrite(); | ||||
|       $this->saveMetadata(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   protected function action(callable $callback, bool $willWrite=false) { | ||||
|     if ($willWrite && !$this->readonly) $this->lockWrite(); | ||||
|     else $this->lockRead(); | ||||
|     try { | ||||
|       $this->beforeAction(); | ||||
|       $result = $callback(); | ||||
|       $this->afterAction(); | ||||
|       return $result; | ||||
|     } finally { | ||||
|       $this->ostart = null; | ||||
|       $this->oduration = null; | ||||
|       $this->odata = null; | ||||
|       $this->start = null; | ||||
|       $this->duration = null; | ||||
|       $this->data = null; | ||||
|       $this->unlock(true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   protected function compute() { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   protected function refreshData($data, bool $noCache) { | ||||
|     $source = $this->sources[$data] ?? null; | ||||
|     $updateMetadata = $this->shouldUpdate($noCache); | ||||
|     if ($source === null) $updateData = $this->data === null; | ||||
|     else $updateData = !$source->exists(); | ||||
|     if (!$this->readonly && ($updateMetadata || $updateData)) { | ||||
|       $this->lockWrite(); | ||||
|       if ($updateMetadata) { | ||||
|         # il faut refaire tout le cache
 | ||||
|         $this->unlinkFiles(true); | ||||
|         $this->start = null; | ||||
|         $this->duration = null; | ||||
|         $this->data = null; | ||||
|         $updateData = true; | ||||
|       } | ||||
|       if ($source === null) { | ||||
|         if ($updateData) { | ||||
|           # calculer la valeur
 | ||||
|           try { | ||||
|             $data = $this->compute(); | ||||
|           } catch (Exception $e) { | ||||
|             # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors
 | ||||
|             # des futurs appels, l'exception continuera d'être lancée ou la
 | ||||
|             # valeur sera finalement mise à jour
 | ||||
|             throw $e; | ||||
|           } | ||||
|         } else { | ||||
|           $data = $this->data; | ||||
|         } | ||||
|         if ($this->shouldCache($data)) $this->data = $data; | ||||
|         else $this->data = $data = null; | ||||
|       } else { | ||||
|         if ($updateData) { | ||||
|           # calculer la valeur
 | ||||
|           try { | ||||
|             $data = $source->compute(); | ||||
|           } catch (Exception $e) { | ||||
|             # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors
 | ||||
|             # des futurs appels, l'exception continuera d'être lancée ou la
 | ||||
|             # valeur sera finalement mise à jour
 | ||||
|             throw $e; | ||||
|           } | ||||
|         } else { | ||||
|           $data = $source->load(); | ||||
|         } | ||||
|         if ($this->shouldCache($data)) { | ||||
|           $data = $source->save($data); | ||||
|         } else { | ||||
|           # ne pas garder le fichier s'il ne faut pas mettre en cache
 | ||||
|           $source->delete(); | ||||
|           $data = null; | ||||
|         } | ||||
|       } | ||||
|     } elseif ($source === null) { | ||||
|       $data = $this->data; | ||||
|     } elseif ($source->exists()) { | ||||
|       $data = $source->load(); | ||||
|     } else { | ||||
|       $data = null; | ||||
|     } | ||||
|     return $data; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * s'assurer que le cache est à jour avec les données les plus récentes. si | ||||
|    * les données sont déjà présentes dans le cache et n'ont pas encore expirées | ||||
|    * cette méthode est un NOP | ||||
|    */ | ||||
|   function refresh(bool $noCache=false): self { | ||||
|     $this->action(function() use ($noCache) { | ||||
|       foreach (array_keys($this->sources) as $data) { | ||||
|         $this->refreshData($data, $noCache); | ||||
|       } | ||||
|     }); | ||||
|     return $this; | ||||
|   } | ||||
| 
 | ||||
|   function get($data=null, bool $noCache=false) { | ||||
|     return $this->action(function () use ($data, $noCache) { | ||||
|       return $this->refreshData($data, $noCache); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function all($data=null, bool $noCache=false): ?iterable { | ||||
|     $data = $this->get($data, $noCache); | ||||
|     if ($data !== null && !is_iterable($data)) $data = [$data]; | ||||
|     return $data; | ||||
|   } | ||||
| 
 | ||||
|   function delete($data=null): void { | ||||
|     $source = $this->sources[$data] ?? null; | ||||
|     if ($source !== null) $source->delete(); | ||||
|   } | ||||
| 
 | ||||
|   /** obtenir les informations sur le fichier */ | ||||
|   function getInfos(): array { | ||||
|     return $this->action(function () { | ||||
|       if (!$this->isValid()) { | ||||
|         return ["valid" => false]; | ||||
|       } | ||||
|       $start = $this->start; | ||||
|       $duration = $this->duration; | ||||
|       return [ | ||||
|         "valid" => true, | ||||
|         "start" => $start, | ||||
|         "duration" => strval($duration), | ||||
|         "date_start" => $start->format(), | ||||
|         "date_end" => $duration->getDest()->format(), | ||||
|       ]; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   const UPDATE_SUB = -1, UPDATE_SET = 0, UPDATE_ADD = 1; | ||||
| 
 | ||||
|   /** | ||||
|    * mettre à jour la durée de validité du fichier | ||||
|    * | ||||
|    * XXX UPDATE_SET n'est pas implémenté | ||||
|    */ | ||||
|   function updateDuration($nduration, int $action=self::UPDATE_ADD): void { | ||||
|     if ($this->readonly) return; | ||||
|     $this->action(function () use ($nduration, $action) { | ||||
|       if (!$this->isValid()) return; | ||||
|       $duration = $this->duration; | ||||
|       if ($action < 0) $duration->subDuration($nduration); | ||||
|       elseif ($action > 0) $duration->addDuration($nduration); | ||||
|     }, true); | ||||
|   } | ||||
| 
 | ||||
|   /** supprimer les fichiers s'ils ont expiré */ | ||||
|   function deleteExpired(bool $force=false): bool { | ||||
|     if ($this->readonly) return false; | ||||
|     return $this->action(function () use ($force) { | ||||
|       if ($force || $this->shouldUpdate()) { | ||||
|         $this->unlinkFiles(); | ||||
|         return true; | ||||
|       } | ||||
|       return false; | ||||
|     }, true); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										68
									
								
								php/src/cache/CacheManager.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								php/src/cache/CacheManager.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| 
 | ||||
| /** | ||||
|  * Class CacheManager: un gestionnaire de cache permettant de désactiver la mise | ||||
|  * en cache d'une valeur dans le cadre d'une session. | ||||
|  * | ||||
|  * en effet, si on désactive le cache, il doit être réactivé après que la valeur | ||||
|  * est calculée, pour éviter qu'une valeur soit calculée encore et encore dans | ||||
|  * une session de travail | ||||
|  */ | ||||
| class CacheManager { | ||||
|   function __construct(?array $includes=null, ?array $excludes=null) { | ||||
|     $this->shouldCaches = []; | ||||
|     $this->defaultCache = true; | ||||
|     $this->includes = $includes; | ||||
|     $this->excludes = $excludes; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @var array tableau {id => shouldCache} indiquant si l'élément id doit être | ||||
|    * mis en cache | ||||
|    */ | ||||
|   protected array $shouldCaches; | ||||
| 
 | ||||
|   /** | ||||
|    * @var bool valeur par défaut de shouldCache si la valeur n'est pas trouvée | ||||
|    * dans $shouldCache | ||||
|    */ | ||||
|   protected bool $defaultCache; | ||||
| 
 | ||||
|   /** | ||||
|    * @var array|null groupes à toujours inclure dans le cache. pour les | ||||
|    * identifiants de ces groupe, {@link self::shouldCache()} retourne toujours | ||||
|    * true. | ||||
|    * | ||||
|    * $excludes est prioritaire par rapport à $includes | ||||
|    */ | ||||
|   protected ?array $includes; | ||||
| 
 | ||||
|   /** | ||||
|    * @var array|null groupes à exclure de la mise en cache. la mise en cache est | ||||
|    * toujours calculée pour les identifiants de ces groupes. | ||||
|    */ | ||||
|   protected ?array $excludes; | ||||
| 
 | ||||
|   function setNoCache(bool $noCache=true, bool $reset=true): self { | ||||
|     if ($reset) $this->shouldCaches = []; | ||||
|     $this->defaultCache = !$noCache; | ||||
|     return $this; | ||||
|   } | ||||
| 
 | ||||
|   function shouldCache(string $id, ?string $groupId=null, bool $reset=true): bool { | ||||
|     if ($groupId !== null) { | ||||
|       $includes = $this->includes; | ||||
|       $shouldInclude = $includes !== null && in_array($groupId, $includes); | ||||
|       $excludes = $this->excludes; | ||||
|       $shouldExclude = $excludes !== null && in_array($groupId, $excludes); | ||||
|       if ($shouldInclude && !$shouldExclude) return true; | ||||
|     } | ||||
|     $cacheId = "$groupId-$id"; | ||||
|     $shouldCache = cl::get($this->shouldCaches, $cacheId, $this->defaultCache); | ||||
|     $this->shouldCaches[$cacheId] = $reset?: $shouldCache; | ||||
|     return $shouldCache; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										36
									
								
								php/src/cache/CursorCacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								php/src/cache/CursorCacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| class CursorCacheData extends CacheData { | ||||
|   function __construct(array $cursorId, $compute=null, ?CursorChannel $channel=null) { | ||||
|     $name = $cursorId["group_id"]; | ||||
|     if ($name) $name .= "_"; | ||||
|     $name .= $cursorId["id"]; | ||||
|     parent::__construct($name, $compute); | ||||
|     $channel ??= (new CursorChannel($cursorId))->initStorage(cache::storage()); | ||||
|     $this->channel = $channel; | ||||
|   } | ||||
| 
 | ||||
|   function setDatafile(?string $basefile): void { | ||||
|   } | ||||
| 
 | ||||
|   protected CursorChannel $channel; | ||||
| 
 | ||||
|   function exists(): bool { | ||||
|     return $this->channel->count() > 0; | ||||
|   } | ||||
| 
 | ||||
|   function load() { | ||||
|     return $this->channel; | ||||
|   } | ||||
| 
 | ||||
|   function save($data) { | ||||
|     if (!is_iterable($data)) $data = [$data]; | ||||
|     $this->channel->rechargeAll($data); | ||||
|     return $this->channel; | ||||
|   } | ||||
| 
 | ||||
|   function delete() { | ||||
|     $this->channel->delete(null); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										127
									
								
								php/src/cache/CursorChannel.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								php/src/cache/CursorChannel.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use IteratorAggregate; | ||||
| use nulib\cl; | ||||
| use nulib\db\CapacitorChannel; | ||||
| use nulib\db\CapacitorStorage; | ||||
| use nulib\php\func; | ||||
| use Traversable; | ||||
| 
 | ||||
| class CursorChannel extends CapacitorChannel implements IteratorAggregate { | ||||
|   static function with($cursorId=null, ?iterable $rows=null, ?CapacitorStorage $storage=null): self { | ||||
|     $storage ??= cache::storage(); | ||||
|     $channel = (new static($cursorId))->initStorage($storage); | ||||
|     if ($rows !== null) $channel->rechargeAll($rows); | ||||
|     return $channel; | ||||
|   } | ||||
| 
 | ||||
|   const NAME = "cursor"; | ||||
|   const TABLE_NAME = "cursor"; | ||||
| 
 | ||||
|   const COLUMN_DEFINITIONS = [ | ||||
|     "group_id_" => "varchar(32) not null", // groupe de curseur
 | ||||
|     "id_" => "varchar(128) not null", // nom du curseur
 | ||||
|     "key_index_" => "integer not null", | ||||
|     "key_" => "varchar(128) not null", | ||||
|     "search_" => "varchar(255)", | ||||
| 
 | ||||
|     "primary key (group_id_, id_, key_index_)", | ||||
|   ]; | ||||
| 
 | ||||
|   const ADD_COLUMNS = null; | ||||
| 
 | ||||
|   protected function COLUMN_DEFINITIONS(): ?array { | ||||
|     return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @param array|string $cursorId | ||||
|    */ | ||||
|   function __construct($cursorId) { | ||||
|     parent::__construct(); | ||||
|     cache::verifix_id($cursorId); | ||||
|     [ | ||||
|       "group_id" => $this->groupId, | ||||
|       "id" => $this->id, | ||||
|     ] = $cursorId; | ||||
|   } | ||||
| 
 | ||||
|   protected string $groupId; | ||||
| 
 | ||||
|   protected string $id; | ||||
| 
 | ||||
|   function getCursorId(): array { | ||||
|     return [ | ||||
|       "group_id" => $this->groupId, | ||||
|       "id" => $this->id, | ||||
|     ]; | ||||
|   } | ||||
| 
 | ||||
|   function getBaseFilter(): ?array { | ||||
|     return [ | ||||
|       "group_id_" => $this->groupId, | ||||
|       "id_" => $this->id, | ||||
|     ]; | ||||
|   } | ||||
| 
 | ||||
|   protected int $index = 0; | ||||
| 
 | ||||
|   protected function getSearch($item): ?string { | ||||
|     $search = cl::filter_n(cl::with($item)); | ||||
|     $search = implode(" ", $search); | ||||
|     return substr($search, 0, 255); | ||||
|   } | ||||
| 
 | ||||
|   function getItemValues($item, $key=null): ?array { | ||||
|     $index = $this->index++; | ||||
|     $key = $key ?? $index; | ||||
|     $key = substr(strval($key), 0, 128); | ||||
|     $addColumns = static::ADD_COLUMNS ?? []; | ||||
|     $addColumns = cl::select($item, | ||||
|       array_filter(array_keys($addColumns), function ($key) { | ||||
|         return is_string($key); | ||||
|       })); | ||||
|     return cl::merge($addColumns, [ | ||||
|       "group_id_" => $this->groupId, | ||||
|       "id_" => $this->id, | ||||
|       "key_index_" => $index, | ||||
|       "key_" => $key, | ||||
|       "search_" => $this->getSearch($item), | ||||
|     ]); | ||||
|   } | ||||
| 
 | ||||
|   function reset(bool $recreate=false): void { | ||||
|     $this->index = 0; | ||||
|     parent::reset($recreate); | ||||
|   } | ||||
| 
 | ||||
|   function chargeAll(?iterable $items, $func=null, ?array $args=null): int { | ||||
|     if ($items === null) return 0; | ||||
|     $count = 0; | ||||
|     if ($func !== null) $func = func::with($func, $args)->bind($this); | ||||
|     foreach ($items as $key => $item) { | ||||
|       $count += $this->charge($item, $func, [$key]); | ||||
|     } | ||||
|     return $count; | ||||
|   } | ||||
| 
 | ||||
|   function rechargeAll(?iterable $items): self { | ||||
|     $this->delete(null); | ||||
|     $this->index = 0; | ||||
|     $this->chargeAll($items); | ||||
|     return $this; | ||||
|   } | ||||
| 
 | ||||
|   function getIterator(): Traversable { | ||||
|     $rows = $this->dbAll([ | ||||
|       "cols" => ["key_", "item__"], | ||||
|       "where" => $this->getBaseFilter(), | ||||
|     ]); | ||||
|     foreach ($rows as $row) { | ||||
|       $key = $row["key_"]; | ||||
|       $item = $this->unserialize($row["item__"]); | ||||
|       yield $key => $item; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										59
									
								
								php/src/cache/DataCacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								php/src/cache/DataCacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\file; | ||||
| use nulib\os\path; | ||||
| use nulib\php\func; | ||||
| use Traversable; | ||||
| 
 | ||||
| class DataCacheData extends CacheData { | ||||
|   /** @var string identifiant de cette donnée */ | ||||
|   const NAME = null; | ||||
| 
 | ||||
|   /** @var callable une fonction permettant de calculer la donnée */ | ||||
|   const COMPUTE = null; | ||||
| 
 | ||||
|   function __construct(?string $name=null, $compute=null, ?string $basefile=null) { | ||||
|     $name ??= static::NAME ?? ""; | ||||
|     $compute ??= static::COMPUTE; | ||||
|     parent::__construct($name, $compute); | ||||
|     $this->setDatafile($basefile); | ||||
|   } | ||||
| 
 | ||||
|   function compute() { | ||||
|     $data = parent::compute(); | ||||
|     if ($data instanceof Traversable) $data = cl::all($data); | ||||
|     return $data; | ||||
|   } | ||||
| 
 | ||||
|   protected string $datafile; | ||||
| 
 | ||||
|   function setDatafile(?string $basefile): void { | ||||
|     if ($basefile === null) { | ||||
|       $basedir = "."; | ||||
|       $basename = ""; | ||||
|     } else { | ||||
|       $basedir = path::dirname($basefile); | ||||
|       $basename = path::filename($basefile); | ||||
|     } | ||||
|     $this->datafile = "$basedir/.$basename.{$this->name}".cache::EXT; | ||||
|   } | ||||
| 
 | ||||
|   function exists(): bool { | ||||
|     return file_exists($this->datafile); | ||||
|   } | ||||
| 
 | ||||
|   function load() { | ||||
|     return file::reader($this->datafile)->unserialize(); | ||||
|   } | ||||
| 
 | ||||
|   function save($data) { | ||||
|     file::writer($this->datafile)->serialize($data); | ||||
|     return $data; | ||||
|   } | ||||
| 
 | ||||
|   function delete(): void { | ||||
|     @unlink($this->datafile); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										6
									
								
								php/src/cache/TODO.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								php/src/cache/TODO.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| # nulib\cache | ||||
| 
 | ||||
| * [ ] CacheChannel: stocker aussi la clé primaire, ce qui permet de récupérer | ||||
|   la donnée correspondante dans la source? | ||||
| 
 | ||||
| -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary | ||||
							
								
								
									
										93
									
								
								php/src/cache/cache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								php/src/cache/cache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\app\app; | ||||
| use nulib\db\CapacitorStorage; | ||||
| use nulib\db\sqlite\SqliteStorage; | ||||
| use nulib\ext\utils; | ||||
| 
 | ||||
| class cache { | ||||
|   /** @var string extension des fichiers de cache */ | ||||
|   const EXT = ".cache"; | ||||
| 
 | ||||
|   protected static ?string $dbfile = null; | ||||
| 
 | ||||
|   protected static function dbfile(): ?string { | ||||
|     return self::$dbfile ??= app::get()->getVarfile("cache.db"); | ||||
|   } | ||||
| 
 | ||||
|   protected static ?CapacitorStorage $storage = null; | ||||
| 
 | ||||
|   static function storage(): CapacitorStorage { | ||||
|     return self::$storage ??= new SqliteStorage(self::dbfile()); | ||||
|   } | ||||
| 
 | ||||
|   static function set_storage(CapacitorStorage $storage): CapacitorStorage { | ||||
|     return self::$storage = $storage; | ||||
|   } | ||||
| 
 | ||||
|   protected static ?CacheManager $manager = null; | ||||
| 
 | ||||
|   static function manager(): CacheManager { | ||||
|     return self::$manager ??= new CacheManager(); | ||||
|   } | ||||
| 
 | ||||
|   static function set_manager(CacheManager $manager): CacheManager { | ||||
|     return self::$manager = $manager; | ||||
|   } | ||||
| 
 | ||||
|   static function nc(bool $noCache=true, bool $reset=false): void { | ||||
|     self::manager()->setNoCache($noCache, $reset); | ||||
|   } | ||||
| 
 | ||||
|   protected static function should_cache(string $id, ?string $groupId=null, bool $reset=true): bool { | ||||
|     return self::manager()->shouldCache($id, $groupId, $reset); | ||||
|   } | ||||
| 
 | ||||
|   static function verifix_id(&$cacheId): void { | ||||
|     $cacheId ??= utils::uuidgen(); | ||||
|     if (is_array($cacheId)) { | ||||
|       $keys = array_keys($cacheId); | ||||
|       if (array_key_exists("id", $cacheId)) $idKey = "id"; | ||||
|       else $idKey = $keys[0] ?? null; | ||||
|       $id = strval($cacheId[$idKey] ?? ""); | ||||
|       if (array_key_exists("group_id", $cacheId)) $groupIdKey = "group_id"; | ||||
|       else $groupIdKey = $keys[1] ?? null; | ||||
|       $groupId = strval($cacheId[$groupIdKey] ?? ""); | ||||
|     } else { | ||||
|       $id = strval($cacheId); | ||||
|       $groupId = ""; | ||||
|     } | ||||
|     # si le groupe ou le nom sont trop grand, en faire un hash
 | ||||
|     if (strlen($groupId) > 32) $groupId = md5($groupId); | ||||
|     if (strlen($id) > 128) $id = substr($id, 0, 128 - 32).md5($id); | ||||
|     $cacheId = ["group_id" => $groupId, "id" => $id]; | ||||
|   } | ||||
| 
 | ||||
|   private static function new(array $cacheId, ?string $suffix, $data, ?array $params=null): CacheFile { | ||||
|     $file = $cacheId["group_id"]; | ||||
|     if ($file) $file .= "_"; | ||||
|     $file .= $cacheId["id"]; | ||||
|     $file .= $suffix; | ||||
|     return new CacheFile($file, $data, $params); | ||||
|   } | ||||
| 
 | ||||
|   static function cache($dataId, $data, ?array $params=null): CacheFile { | ||||
|     self::verifix_id($dataId); | ||||
|     return self::new($dataId, null, $data, $params); | ||||
|   } | ||||
| 
 | ||||
|   static function get($dataId, $data, ?array $params=null) { | ||||
|     self::verifix_id($dataId); | ||||
|     $noCache = !self::should_cache($dataId["id"], $dataId["group_id"]); | ||||
|     $cache = self::new($dataId, null, $data, $params); | ||||
|     return $cache->get(null, $noCache); | ||||
|   } | ||||
| 
 | ||||
|   static function all($cursorId, $rows, ?array $params=null): ?iterable { | ||||
|     self::verifix_id($cursorId); | ||||
|     $noCache = !self::should_cache($cursorId["id"], $cursorId["group_id"]); | ||||
|     $cache = self::new($cursorId, "_rows", new CursorCacheData($cursorId, $rows), $params); | ||||
|     return $cache->get(null, $noCache); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1
									
								
								php/tbin/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								php/tbin/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | ||||
| /*.db | ||||
| /*.cache | ||||
|  | ||||
							
								
								
									
										73
									
								
								php/tbin/test-cache.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										73
									
								
								php/tbin/test-cache.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,73 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\cache\DataCacheData; | ||||
| use nulib\cache\CacheFile; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\sh; | ||||
| 
 | ||||
| function show(string $prefix, CacheFile $cache, bool $dumpInfos=true): void { | ||||
|   Txx("$prefix=", $cache->get()); | ||||
|   if ($dumpInfos) { | ||||
|     yaml::dump($cache->getInfos()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| //system("rm -f *.cache .*.cache");
 | ||||
| 
 | ||||
| $what = [ | ||||
|   "null", | ||||
|   "one", | ||||
|   "two", | ||||
|   "three", | ||||
| ]; | ||||
| $duration = 10; | ||||
| 
 | ||||
| if (in_array("null", $what)) { | ||||
|   $null = new CacheFile("null", null, [ | ||||
|     "duration" => $duration, | ||||
|   ]); | ||||
|   show("null", $null); | ||||
| } | ||||
| 
 | ||||
| if (in_array("one", $what)) { | ||||
|   $one = new class("one", null, [ | ||||
|     "duration" => $duration, | ||||
|   ]) extends CacheFile { | ||||
|     protected function compute() { | ||||
|       return 1; | ||||
|     } | ||||
|   }; | ||||
|   show("one", $one); | ||||
| } | ||||
| 
 | ||||
| if (in_array("two", $what)) { | ||||
|   $two = new CacheFile("two", new DataCacheData(null, function () { | ||||
|     return 2; | ||||
|   }), [ | ||||
|     "duration" => $duration, | ||||
|   ]); | ||||
|   show("two", $two); | ||||
| } | ||||
| 
 | ||||
| if (in_array("three", $what)) { | ||||
|   $data31 = new DataCacheData("data31name", function () { | ||||
|     return 31; | ||||
|   }); | ||||
| 
 | ||||
|   $data32 = new DataCacheData(null, function () { | ||||
|     return 32; | ||||
|   }); | ||||
| 
 | ||||
|   $three = new CacheFile("three", [ | ||||
|     "data31" => $data31, | ||||
|     $data31, # name=data31name
 | ||||
|     "data32" => $data32, | ||||
|     $data32, # name=""
 | ||||
|   ]); | ||||
|   Txx("three.0=", $three->get("data31")); | ||||
|   Txx("three.1=", $three->get("data31name")); | ||||
|   Txx("three.2=", $three->get("data32")); | ||||
|   Txx("three.3=", $three->get("")); | ||||
| } | ||||
							
								
								
									
										38
									
								
								php/tests/cache/CursorChannelTest.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								php/tests/cache/CursorChannelTest.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class CursorChannelTest extends _TestCase { | ||||
|   const DATA = [ | ||||
|     "fr" => ["a" => "un", "b" => "deux"], | ||||
|     "eng" => ["a" => "one", "b" => "two"], | ||||
|     ["a" => 1, "b" => 2], | ||||
|   ]; | ||||
| 
 | ||||
|   function testUsage() { | ||||
|     $channel = CursorChannel::with("numbers", self::DATA, self::$storage); | ||||
|     $count = 0; | ||||
|     foreach ($channel as $key => $item) { | ||||
|       msg::info("one: $key => {$item["a"]}"); | ||||
|       $count++; | ||||
|     } | ||||
|     self::assertSame(3, $count); | ||||
|   } | ||||
| 
 | ||||
|   function testAddColumns() { | ||||
|     $channel = (new class("numbers") extends CursorChannel { | ||||
|       const NAME = "numbersac"; | ||||
|       const TABLE_NAME = self::NAME; | ||||
|       const ADD_COLUMNS = [ | ||||
|         "a" => "varchar(30)", | ||||
|       ]; | ||||
|     })->initStorage(self::$storage)->rechargeAll(self::DATA); | ||||
|     $count = 0; | ||||
|     foreach ($channel as $key => $item) { | ||||
|       msg::info("one: $key => {$item["a"]}"); | ||||
|       $count++; | ||||
|     } | ||||
|     self::assertSame(3, $count); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										22
									
								
								php/tests/cache/SourceDb.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								php/tests/cache/SourceDb.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\db\sqlite\Sqlite; | ||||
| 
 | ||||
| class SourceDb extends Sqlite { | ||||
|   const MIGRATION = [ | ||||
|     "create table source (pk integer primary key autoincrement, s varchar(255), i integer, b boolean)", | ||||
|     [self::class, "fill_data"], | ||||
|   ]; | ||||
| 
 | ||||
|   static function fill_data(Sqlite $db): void { | ||||
|     $db->exec("insert into source (s, i, b) values (null, null, null)"); | ||||
|     $db->exec("insert into source (s, i, b) values ('false', 0, 0)"); | ||||
|     $db->exec("insert into source (s, i, b) values ('first', 1, 1)"); | ||||
|     $db->exec("insert into source (s, i, b) values ('second', 2, 1)"); | ||||
|   } | ||||
| 
 | ||||
|   public function __construct() { | ||||
|     parent::__construct(__DIR__."/source.db"); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										23
									
								
								php/tests/cache/_TestCase.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								php/tests/cache/_TestCase.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\db\sqlite\SqliteStorage; | ||||
| use nulib\output\msg; | ||||
| use nulib\output\std\StdMessenger; | ||||
| use nulib\tests\TestCase; | ||||
| 
 | ||||
| class _TestCase extends TestCase { | ||||
|   protected static SqliteStorage $storage; | ||||
| 
 | ||||
|   static function setUpBeforeClass(): void { | ||||
|     parent::setUpBeforeClass(); | ||||
|     msg::set_messenger_class(StdMessenger::class); | ||||
|     self::$storage = new SqliteStorage(__DIR__."/cache.db"); | ||||
|     cache::set_storage(self::$storage); | ||||
|   } | ||||
| 
 | ||||
|   static function tearDownAfterClass(): void { | ||||
|     parent::tearDownAfterClass(); | ||||
|     self::$storage->close(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										108
									
								
								php/tests/cache/cacheTest.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								php/tests/cache/cacheTest.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| <?php | ||||
| namespace nulib\cache; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class cacheTest extends _TestCase { | ||||
|   const DATA = [ | ||||
|     "fr" => ["a" => "un", "b" => "deux"], | ||||
|     "eng" => ["a" => "one", "b" => "two"], | ||||
|     ["a" => 1, "b" => 2], | ||||
|   ]; | ||||
| 
 | ||||
|   function gendata() { | ||||
|     msg::note("gendata"); | ||||
|     foreach (self::DATA as $key => $item) { | ||||
|       msg::info("yield $key"); | ||||
|       yield $key => $item; | ||||
|       sleep(2); | ||||
|     } | ||||
|     msg::note("fin gendata"); | ||||
|   } | ||||
| 
 | ||||
|   function _testRows(iterable $rows, int $expectedCount) { | ||||
|     $count = 0; | ||||
|     foreach ($rows as $key => $row) { | ||||
|       $parts = ["got $key => {"]; | ||||
|       $i = 0; | ||||
|       foreach ($row as $k => $v) { | ||||
|         if ($i++ > 0) $parts[] = ", "; | ||||
|         $parts[] = "$k=$v"; | ||||
|       } | ||||
|       $parts[] = "}"; | ||||
|       msg::info(implode("", $parts)); | ||||
|       $count++; | ||||
|     } | ||||
|     self::assertSame($expectedCount, $count); | ||||
|   } | ||||
|    | ||||
|   function _testGet(string $dataId, int $expectedCount, callable $gencompute) { | ||||
|     msg::section($dataId); | ||||
|     cache::nc(true, true); | ||||
| 
 | ||||
|     msg::step("premier"); | ||||
|     $rows = cache::get($dataId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|     msg::step("deuxième"); | ||||
|     $rows = cache::get($dataId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
| 
 | ||||
|     msg::step("vider le cache"); | ||||
|     cache::nc(true, true); | ||||
| 
 | ||||
|     msg::step("premier"); | ||||
|     $rows = cache::get($dataId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|     msg::step("deuxième"); | ||||
|     $rows = cache::get($dataId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|   } | ||||
| 
 | ||||
|   function testGetStatic() { | ||||
|     $this->_testGet("getStatic", 3, function () { | ||||
|       return static function () { | ||||
|         msg::note("getdata"); | ||||
|         return self::DATA; | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function testGetGenerator() { | ||||
|     $this->_testGet("getGenerator", 3, function () { | ||||
|       return $this->gendata(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function _testAll(string $cursorId, int $expectedCount, callable $gencompute) { | ||||
|     msg::section($cursorId); | ||||
|     cache::nc(true, true); | ||||
| 
 | ||||
|     msg::step("premier"); | ||||
|     $rows = cache::all($cursorId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|     msg::step("deuxième"); | ||||
|     $rows = cache::all($cursorId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
| 
 | ||||
|     msg::step("vider le cache"); | ||||
|     cache::nc(true, true); | ||||
| 
 | ||||
|     msg::step("premier"); | ||||
|     $rows = cache::all($cursorId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|     msg::step("deuxième"); | ||||
|     $rows = cache::all($cursorId, $gencompute()); | ||||
|     $this->_testRows($rows, $expectedCount); | ||||
|   } | ||||
| 
 | ||||
|   function testAllGenerator() { | ||||
|     $this->_testAll("allGenerator", 4, function() { | ||||
|       return static function() { | ||||
|         $db = new SourceDb(); | ||||
|         msg::note("query source"); | ||||
|         yield from $db->all("select * from source"); | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user