modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2025-10-05 15:25:38 +04:00
parent 11c761832a
commit 36bae9ef7d
7 changed files with 216 additions and 207 deletions

View File

@ -1,50 +0,0 @@
<?php
namespace nulib\cache;
use nulib\cl;
use nulib\php\func;
use Traversable;
class 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($compute=null, ?string $name=null) {
$this->name = $name ?? static::NAME ?? "";
$this->compute = func::withn($compute ?? static::COMPUTE);
}
protected string $name;
protected ?func $compute;
protected function compute() {
$compute = $this->compute;
return $compute !== null? $compute->invoke(): null;
}
function getName() : string {
return $this->name;
}
/** obtenir la donnée, en l'itérant au préalable si elle est traversable */
function get($compute=null) {
$this->compute ??= func::withn($compute);
$data = $this->compute();
if ($data instanceof Traversable) {
$data = cl::all($data);
}
return $data;
}
/** obtenir un itérateur sur la donnée ou null s'il n'y a pas de données */
function all($compute=null): ?iterable {
$this->compute ??= func::withn($compute);
$data = $this->compute();
if ($data !== null && !is_iterable($data)) $data = [$data];
return $data;
}
}

View File

@ -17,63 +17,70 @@ 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
const EXT = ".cache";
static function with($data, ?string $file=null): self {
if ($data instanceof self) return $data;
else return new static($file, $data);
}
protected function ensure_source($source): CacheData {
if ($source instanceof CacheData) return $source;
if (cv::subclass_of($source, CacheData::class)) return new $source();
if (func::is_callable($source)) return new CacheData($source);
throw ValueException::invalid_type($source, CacheData::class);
protected static function ensure_source($data, ?IDataCache &$source, bool $allowArray=true): bool {
if ($data === null || $data instanceof IDataCache) {
$source = $data;
} elseif (is_subclass_of($data, IDataCache::class)) {
$source = new $data();
} elseif (func::is_callable($data)) {
$source = new DataCache(null, $data);
} elseif (is_array($data) && $allowArray) {
return false;
} elseif (is_iterable($data)) {
$source = new DataCache(null, static function() use ($data) {
yield from $data;
});
} else {
throw ValueException::invalid_type($source, IDataCache::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, self::EXT);
$this->basedir = path::dirname($file);
$basename = path::filename($file);
$this->basename = str::without_suffix(self::EXT, $basename);
$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 ($data === null) {
$this->source = null;
} elseif ($data instanceof CacheData) {
$this->source = $data;
} elseif (func::is_callable($data)) {
$this->source = new CacheData($data);
} elseif (!is_array($data)) {
$this->source = self::ensure_source($data);
if (self::ensure_source($data, $source)) {
if ($source !== null) {
$source->setDatafile($basefile);
$key = $source->getName();
} else {
$key = "";
}
$this->sources = [$key => $source];
} else {
$sources = [];
$index = 0;
foreach ($data as $key => $source) {
$source = self::ensure_source($source);
if ($key === $index) {
self::ensure_source($source, $source, false);
if ($source !== null) {
$source->setDatafile($basefile);
if ($key === $index) {
$index++;
$key = $source->getName();
}
} elseif ($key === $index) {
$index++;
$key = $source->getName();
}
$sources[$key] = $source;
}
$this->sources = $sources;
$this->source = null;
}
parent::__construct($file);
}
/** @var string répertoire de base des fichiers de cache */
protected string $basedir;
/** @var string nom de base des fichiers de cache */
protected string $basename;
protected Delay $initialDuration;
protected bool $overrideDuration;
@ -82,25 +89,9 @@ class CacheFile extends SharedFile {
protected bool $cacheNull;
/** @var ?IDataCache[] */
protected ?array $sources;
protected ?CacheData $source;
protected function getSource($data): CacheData {
if ($data === null) {
return $this->source ??= new CacheData(function() {
return $this->compute();
});
}
$source = $data;
if (is_string($source) || is_int($source)) {
$source = $this->sources[$source] ?? null;
if ($source === null) throw ValueException::invalid_key($data);
}
if ($source instanceof CacheData) return $source;
throw ValueException::invalid_type($data, CacheData::class);
}
/**
* vérifier si le fichier est valide. s'il est invalide, il faut le recréer.
*
@ -117,8 +108,6 @@ class CacheFile extends SharedFile {
protected $data;
protected ?array $datafilenames;
/** charger les données. le fichier a déjà été verrouillé en lecture */
protected function loadMetadata(): void {
if ($this->isValid()) {
@ -127,22 +116,18 @@ class CacheFile extends SharedFile {
"start" => $start,
"duration" => $duration,
"data" => $data,
"datafiles" => $datafilenames,
] = $this->unserialize(null, false, true);
if ($this->overrideDuration) {
$duration = Delay::with($this->initialDuration, $start);
}
$datafilenames = array_fill_keys($datafilenames, true);
} else {
$start = null;
$duration = null;
$data = null;
$datafilenames = [];
}
$this->start = $start;
$this->duration = $duration;
$this->data = $data;
$this->datafilenames = $datafilenames;
}
/**
@ -166,33 +151,17 @@ class CacheFile extends SharedFile {
$this->start = new DateTime();
$this->duration = Delay::with($this->duration, $this->start);
}
$datafilenames = array_keys($this->datafilenames);
$this->ftruncate();
$this->serialize([
"start" => $this->start,
"duration" => $this->duration,
"data" => $this->data,
"datafiles" => $datafilenames,
], false, true);
}
protected function loadDatafile(string $datafile) {
return file::reader($datafile)->unserialize();
}
protected function saveDatafile(string $datafile, $data): void {
file::writer($datafile)->serialize($data);
}
protected function unlinkDatafile(string $datafilename): void {
$datafile = path::join($this->basedir, $datafilename);
@unlink($datafile);
unset($this->datafilenames[$datafilename]);
}
protected function unlinkFiles(bool $datafilesOnly=false): void {
foreach ($this->datafilenames as $datafilename) {
$this->unlinkDatafile($datafilename);
foreach ($this->sources as $source) {
if ($source !== null) $source->delete();
}
if (!$datafilesOnly) @unlink($this->file);
}
@ -208,14 +177,11 @@ class CacheFile extends SharedFile {
protected $odata;
protected ?array $odatafilenames;
protected function beforeAction() {
$this->loadMetadata();
$this->ostart = cv::clone($this->start);
$this->oduration = cv::clone($this->duration);
$this->odata = cv::clone($this->data);
$this->odatafilenames = cv::clone($this->datafilenames);
}
protected function afterAction() {
@ -227,7 +193,6 @@ class CacheFile extends SharedFile {
elseif ($duration->getDest() != $oduration->getDest()) $modified = true;
# égalité stricte uniquement pour $data et $datafiles
if ($this->data !== $this->odata) $modified = true;
if ($this->datafilenames !== $this->odatafilenames) $modified = true;
if ($modified && !$this->readonly) {
$this->lockWrite();
$this->saveMetadata();
@ -246,11 +211,9 @@ class CacheFile extends SharedFile {
$this->ostart = null;
$this->oduration = null;
$this->odata = null;
$this->odatafilenames = null;
$this->start = null;
$this->duration = null;
$this->data = null;
$this->datafilenames = null;
$this->unlock(true);
}
}
@ -260,15 +223,10 @@ class CacheFile extends SharedFile {
}
protected function refreshData($data, bool $noCache) {
$defaultSource = $data === null;
$source = $this->getSource($data);
$dataname = $source->getName();
$datafilename = ".{$this->basename}.{$dataname}".self::EXT;
$datafile = path::join($this->basedir, $datafilename);
$source = $this->sources[$data] ?? null;
$updateMetadata = $this->shouldUpdate($noCache);
if ($defaultSource) $updateData = $this->data === null;
else $updateData = !array_key_exists($datafilename, $this->datafilenames);
if ($source === null) $updateData = $this->data === null;
else $updateData = !$source->exists();
if (!$this->readonly && ($updateMetadata || $updateData)) {
$this->lockWrite();
if ($updateMetadata) {
@ -279,11 +237,11 @@ class CacheFile extends SharedFile {
$this->data = null;
$updateData = true;
}
if ($defaultSource) {
if ($source === null) {
if ($updateData) {
# calculer la valeur
try {
$data = $source->get();
$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
@ -299,29 +257,28 @@ class CacheFile extends SharedFile {
if ($updateData) {
# calculer la valeur
try {
$data = $source->get();
$data = $source->compute();
} catch (Exception $e) {
# ne pas garder le fichier destination en cas d'exception
$this->unlinkDatafile($datafile);
# 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;
}
} elseif (file_exists($datafile)) {
$data = $this->loadDatafile($datafile);
} else {
$data = null;
$data = $source->load();
}
if ($this->shouldCache($data)) {
$this->saveDatafile($datafile, $data);
$this->datafilenames[$datafilename] = true;
$source->save($data);
} else {
# ne pas garder le fichier s'il ne faut pas mettre en cache
$this->unlinkDatafile($datafile);
$source->delete();
$data = null;
}
}
} elseif ($defaultSource) {
} elseif ($source === null) {
$data = $this->data;
} elseif (file_exists($datafile)) {
$data = $this->loadDatafile($datafile);
} elseif ($source->exists()) {
$data = $source->load();
} else {
$data = null;
}
@ -335,10 +292,7 @@ class CacheFile extends SharedFile {
*/
function refresh(bool $noCache=false): self {
$this->action(function() use ($noCache) {
$source = $this->source;
if ($source !== null) $datas = [null];
else $datas = array_keys($this->sources);
foreach ($datas as $data) {
foreach (array_keys($this->sources) as $data) {
$this->refreshData($data, $noCache);
}
});
@ -358,10 +312,8 @@ class CacheFile extends SharedFile {
}
function delete($data=null): void {
$source = $this->getSource($data);
$dataname = $source->getName();
$datafilename = ".{$this->basename}.{$dataname}".self::EXT;
$this->unlinkDatafile($datafilename);
$source = $this->sources[$data] ?? null;
if ($source !== null) $source->delete();
}
/** obtenir les informations sur le fichier */
@ -372,27 +324,12 @@ class CacheFile extends SharedFile {
}
$start = $this->start;
$duration = $this->duration;
$datafilenames = $this->datafilenames;
$datafiles = [];
foreach (array_keys($datafilenames) as $datafilename) {
$datafile = path::join($this->basedir, $datafilename);
$name = $datafilename;
str::del_prefix($name, ".{$this->basename}.");
str::del_suffix($name, self::EXT);
if (file_exists($datafile)) {
$size = filesize($datafile);
} else {
$size = null;
}
$datafiles[$name] = $size;
}
return [
"valid" => true,
"start" => $start,
"duration" => strval($duration),
"date_start" => $start->format(),
"date_end" => $duration->getDest()->format(),
"datafiles" => $datafiles,
];
});
}

66
src/cache/DataCache.php vendored Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace nulib\cache;
use nulib\cl;
use nulib\file;
use nulib\os\path;
use nulib\php\func;
use Traversable;
class DataCache implements IDataCache {
/** @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) {
$this->name = $name ?? static::NAME ?? "";
$this->compute = func::withn($compute ?? static::COMPUTE);
$this->setDatafile($basefile);
}
protected string $name;
function getName() : string {
return $this->name;
}
protected ?func $compute;
function compute() {
$compute = $this->compute;
$data = $compute !== null? $compute->invoke(): null;
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): void {
file::writer($this->datafile)->serialize($data);
}
function delete(): void {
@unlink($this->datafile);
}
}

27
src/cache/IDataCache.php vendored Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace nulib\cache;
/**
* Interface IDataCache: gestion d'une donnée mise en cache
*/
interface IDataCache {
function getName(): string;
/** calculer la donnée */
function compute();
/** spécifier le chemin du cache à partir du fichier de base */
function setDatafile(?string $basefile): void;
/** indiquer si le cache existe */
function exists(): bool;
/** charger la donnée depuis le cache */
function load();
/** sauvegarder la donnée dans le cache */
function save($data): void;
/** supprimer le cache */
function delete();
}

34
src/cache/cache.php vendored
View File

@ -7,6 +7,9 @@ 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 {
@ -61,21 +64,30 @@ class cache {
$cacheId = ["group_id" => $groupId, "id" => $id];
}
static function get($dataId, callable $compute, ?array $params=null) {
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, callable $compute, ?array $params=null): CacheFile {
self::verifix_id($dataId);
return self::new($dataId, null, $compute, $params);
}
static function get($dataId, $data, ?array $params=null) {
self::verifix_id($dataId);
$file = "{$dataId["group_id"]}_{$dataId["id"]}";
$noCache = !self::should_cache($dataId["id"], $dataId["group_id"]);
return (new CacheFile($file, $compute, $params))->get(null, $noCache);
$cache = self::new($dataId, null, $data, $params);
return $cache->get(null, $noCache);
}
static function all($cursorId, ?iterable $rows, ?array $params=null): iterable {
self::verifix_id($cursorId);
$file = "{$cursorId["group_id"]}_{$cursorId["id"]}_rows";
$ccursorId = new CacheFile($file, function() use ($cursorId, $rows) {
CursorChannel::with($cursorId)->rechargeAll($rows);
return $cursorId;
}, $params);
$noCache = !self::should_cache($cursorId["id"], $cursorId["group_id"]);
return CursorChannel::with($ccursorId->get(null, $noCache));
self::verifix_id($dataId);
$noCache = !self::should_cache($dataId["id"], $dataId["group_id"]);
$cache = self::new($cursorId, "_rows", new CursorCache($cursorId, $rows), $params);
return $cache->get(null, $noCache);
}
}

View File

@ -2,7 +2,7 @@
<?php
require __DIR__.'/../vendor/autoload.php';
use nulib\cache\CacheData;
use nulib\cache\DataCache;
use nulib\cache\CacheFile;
use nulib\ext\yaml;
use nulib\os\sh;
@ -43,7 +43,7 @@ if (in_array("one", $what)) {
}
if (in_array("two", $what)) {
$two = new CacheFile("two", new CacheData(function () {
$two = new CacheFile("two", new DataCache(null, function () {
return 2;
}), [
"duration" => $duration,
@ -52,11 +52,11 @@ if (in_array("two", $what)) {
}
if (in_array("three", $what)) {
$data31 = new CacheData(function () {
$data31 = new DataCache("data31name", function () {
return 31;
}, "data31name");
});
$data32 = new CacheData(function () {
$data32 = new DataCache(null, function () {
return 32;
});

View File

@ -12,44 +12,61 @@ class cacheTest extends _TestCase {
];
function gendata() {
msg::note("gendata");
foreach (self::DATA as $key => $item) {
msg::info("yield $key");
yield $key => $item;
sleep(2);
}
msg::info("fin gendata");
msg::note("fin gendata");
}
function _testRows(iterable $rows) {
$count = 0;
foreach ($rows as $key => $row) {
msg::info("got $key => ".var_export($row, true));
msg::info("got $key => {a={$row["a"]}, b={$row["b"]}}");
$count++;
}
self::assertSame(3, $count);
}
function testUsage() {
msg::section("all");
$rows = cache::all("gendata", $this->gendata());
$this->_testRows($rows);
msg::section("get");
$rows = cache::get("gendata", function () {
return self::DATA;
});
$this->_testRows($rows);
}
function testNc() {
function _testGet(string $dataId, callable $gencompute) {
msg::section($dataId);
cache::nc();
msg::section("first pass");
$rows = cache::all("gendata", $this->gendata());
msg::step("premier");
$rows = cache::get($dataId, $gencompute());
$this->_testRows($rows);
msg::step("deuxième");
$rows = cache::get($dataId, $gencompute());
$this->_testRows($rows);
msg::section("second pass");
$rows = cache::all("gendata", $this->gendata());
msg::step("vider le cache");
cache::nc(true, true);
msg::step("premier");
$rows = cache::get($dataId, $gencompute());
$this->_testRows($rows);
msg::step("deuxième");
$rows = cache::get($dataId, $gencompute());
$this->_testRows($rows);
}
function testGetStatic() {
$this->_testGet("getStatic", function() {
return static function () {
msg::note("getdata");
return self::DATA;
};
});
}
function testGetGenerator() {
$this->_testGet("getGenerator", function() {
return $this->gendata();
});
}
function testAll() {
}
}