From 6fcb32bd25c9ff223f7197cfe4264a5b0f4cecaf Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 27 Jun 2025 18:29:42 +0400 Subject: [PATCH 01/10] modifs.mineures sans commentaires --- nur_src/v/icon.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nur_src/v/icon.php b/nur_src/v/icon.php index 1d20ac1..b062e7a 100644 --- a/nur_src/v/icon.php +++ b/nur_src/v/icon.php @@ -99,6 +99,9 @@ class icon { static final function plus($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("plus", $suffix, $alt); } static final function minus($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("minus", $suffix, $alt); } + static final function start($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("play", $suffix, $alt); } + static final function stop($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("stop", $suffix, $alt); } + # template: #static final function xxx($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("xxx", $suffix, $alt); } } From d50932612fd49be26361382aef6b18a541bf0aea Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 28 Jun 2025 14:40:49 +0400 Subject: [PATCH 02/10] modifs.mineures sans commentaires --- src/cache/CacheFile.php | 97 ++++++++++++++++++++-------------- src/cli/AbstractStorageApp.php | 2 +- src/cli/NucacheApp.php | 4 +- tbin/test-nucache.php | 27 +++++----- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/cache/CacheFile.php b/src/cache/CacheFile.php index 3feb150..18cc295 100644 --- a/src/cache/CacheFile.php +++ b/src/cache/CacheFile.php @@ -25,7 +25,7 @@ class CacheFile extends SharedFile { throw ValueException::invalid_type($source, CacheData::class); } - function __construct($file, ?array $params=null) { + function __construct($file, $data=null, ?array $params=null) { if ($file === null) { $rand = bin2hex(random_bytes(8)); $file = path::join(sys_get_temp_dir(), $rand); @@ -38,7 +38,7 @@ class CacheFile extends SharedFile { $this->overrideDuration = $params["override_duration"] ?? false; $this->readonly = $params["readonly"] ?? false; $this->cacheNull = $params["cache_null"] ?? false; - $data = $params["data"] ?? null; + $data ??= $params["data"] ?? null; $this->sources = null; if ($data === null) { $this->source = null; @@ -243,51 +243,70 @@ class CacheFile extends SharedFile { return null; } - function get($data=null, bool $noCache=false) { - return $this->action(function () use ($data, $noCache) { - $source = $this->getSource($data); - $dataname = $source->getName(); - $datafilename = ".{$this->basename}.{$dataname}".self::EXT; - $datafile = path::join($this->basedir, $datafilename); + protected function refreshData($data, bool $noCache) { + $source = $this->getSource($data); + $dataname = $source->getName(); + $datafilename = ".{$this->basename}.{$dataname}".self::EXT; + $datafile = path::join($this->basedir, $datafilename); - $updateMetadata = $this->shouldUpdate($noCache); - $updateData = !array_key_exists($datafilename, $this->datafilenames); - if (!$this->readonly && ($updateMetadata || $updateData)) { - $this->lockWrite(); - if ($updateMetadata) { - # il faut refaire tout le cache - $this->unlinkFiles(true); - $this->start = null; - $this->duration = null; - $updateData = true; - } - if ($updateData) { - # calculer un fichier - try { - $data = $source->get(); - } catch (Exception $e) { - # ne pas garder le fichier en cas d'exception - $this->unlinkDatafile($datafile); - throw $e; - } - } elseif (file_exists($datafile)) { - $data = $this->loadData($datafile); - } else { - $data = null; - } - if ($this->shouldCache($data)) { - $this->saveData($datafile, $data); - $this->datafilenames[$datafilename] = true; - } else { - # ne pas garder le fichier s'il ne faut pas mettre en cache + $updateMetadata = $this->shouldUpdate($noCache); + $updateData = !array_key_exists($datafilename, $this->datafilenames); + if (!$this->readonly && ($updateMetadata || $updateData)) { + $this->lockWrite(); + if ($updateMetadata) { + # il faut refaire tout le cache + $this->unlinkFiles(true); + $this->start = null; + $this->duration = null; + $updateData = true; + } + if ($updateData) { + # calculer un fichier + try { + $data = $source->get(); + } catch (Exception $e) { + # ne pas garder le fichier en cas d'exception $this->unlinkDatafile($datafile); + throw $e; } } elseif (file_exists($datafile)) { $data = $this->loadData($datafile); } else { $data = null; } - return $data; + if ($this->shouldCache($data)) { + $this->saveData($datafile, $data); + $this->datafilenames[$datafilename] = true; + } else { + # ne pas garder le fichier s'il ne faut pas mettre en cache + $this->unlinkDatafile($datafile); + } + } elseif (file_exists($datafile)) { + $data = $this->loadData($datafile); + } 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 { + $source = $this->source; + if ($source !== null) $datas = [null]; + else $datas = array_keys($this->sources); + foreach ($datas 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); }); } diff --git a/src/cli/AbstractStorageApp.php b/src/cli/AbstractStorageApp.php index 9526d74..81504b1 100644 --- a/src/cli/AbstractStorageApp.php +++ b/src/cli/AbstractStorageApp.php @@ -98,7 +98,7 @@ abstract class AbstractStorageApp extends Application { } } $first = true; - $capacitor->each($filter, function ($item, $row) use (&$first) { + $capacitor->each($filter, function ($row) use (&$first) { if ($first) $first = false; else echo "---\n"; yaml::dump($row); diff --git a/src/cli/NucacheApp.php b/src/cli/NucacheApp.php index 7248f47..6845ee5 100644 --- a/src/cli/NucacheApp.php +++ b/src/cli/NucacheApp.php @@ -87,7 +87,7 @@ class NucacheApp extends Application { switch ($this->action) { case self::ACTION_READ: if ($showSection) msg::section($file); - $cache = new CacheFile($file, [ + $cache = new CacheFile($file, null, [ "readonly" => true, "duration" => "INF", "override_duration" => true, @@ -96,7 +96,7 @@ class NucacheApp extends Application { break; case self::ACTION_INFOS: if ($showSection) msg::section($file); - $cache = new CacheFile($file, [ + $cache = new CacheFile($file, null, [ "readonly" => true, ]); yaml::dump($cache->getInfos()); diff --git a/tbin/test-nucache.php b/tbin/test-nucache.php index 802926f..d148ee6 100755 --- a/tbin/test-nucache.php +++ b/tbin/test-nucache.php @@ -17,22 +17,22 @@ function show(string $prefix, CacheFile $cache, bool $dumpInfos=true): void { //system("rm -f *.cache .*.cache"); $what = [ - //"null", + "null", "one", - //"two", - //"three", + "two", + "three", ]; $duration = 10; if (in_array("null", $what)) { - $null = new CacheFile("null", [ + $null = new CacheFile("null", null, [ "duration" => $duration, ]); show("null", $null); } if (in_array("one", $what)) { - $one = new class("one", [ + $one = new class("one", null, [ "duration" => $duration, ]) extends CacheFile { protected function compute() { @@ -43,11 +43,10 @@ if (in_array("one", $what)) { } if (in_array("two", $what)) { - $two = new CacheFile("two", [ + $two = new CacheFile("two", new CacheData(function () { + return 2; + }), [ "duration" => $duration, - "data" => new CacheData(function () { - return 2; - }), ]); show("two", $two); } @@ -62,12 +61,10 @@ if (in_array("three", $what)) { }); $three = new CacheFile("three", [ - "data" => [ - "data31" => $data31, - $data31, # name=data31name - "data32" => $data32, - $data32, # name="" - ], + "data31" => $data31, + $data31, # name=data31name + "data32" => $data32, + $data32, # name="" ]); Txx("three.0=", $three->get("data31")); Txx("three.1=", $three->get("data31name")); From 7bd7c0dc94a37a3ac89129077c23b88bb49cf4ac Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 29 Jun 2025 12:42:56 +0400 Subject: [PATCH 03/10] modifs.mineures sans commentaires --- TODO.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/TODO.md b/TODO.md index 1c71852..c5f44bb 100644 --- a/TODO.md +++ b/TODO.md @@ -73,6 +73,38 @@ ~~~ bien entendu, bien que ce ne soit pas démontré ici, le premier argument (l'en-tête) est un contenu, pas seulement une chaine + + chaque élément de contenu défini header, body et footer + ~~~php + $element = ["header" => [], "body" => [], "footer" => []]; + ~~~ + + colspan peut être utilisé dans header, body ou footer. si spécifié dans + $element, colspan s'applique à body + ~~~php + # les deux suivants sont équivalents + $element = ["myheader", "mybody", "colspan" => 2, "myfooter"]; + $element = [ + "header" => [ + "content" => "myheader", + ], + "body" => [ + "content" => "mybody", + "colspan" => 2, + ], + "footer" => [ + "content" => "myfooter", + ], + ]; + ~~~ + + si $element est une fonction, il retourne la valeur de l'élement + ~~~php + $element = function() { + return ["header", "body"]; + } + ~~~ + * Cursor::dyn permet d'insérer une valeur qui sera évaluée plus tard lors de la résolution du contenu * pour Cursor, CTable, etc., un paramètre "params_func" permet de générer une From 562248fce9018c520ffe0868a47e8a70a92a84e6 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 29 Jun 2025 18:08:39 +0400 Subject: [PATCH 04/10] modifs.mineures sans commentaires --- TODO.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/TODO.md b/TODO.md index c5f44bb..a085552 100644 --- a/TODO.md +++ b/TODO.md @@ -105,6 +105,20 @@ } ~~~ + clé "enabled" permet d'activer dynamiquement un élément + ~~~php + $element = function() { + return ["header", "body", "enabled" => $includeElement]; + } + ~~~ + + clé "func" pour spécifier la fonction dans un contexte statique + ~~~php + $element = ["func" => [My::class, "get_element"]]; + ~~~ + func n'est considéré que si $element était un tableau à la base + s'il y a d'autres clés, elles sont fusionnées avec le résultat de $func() (dont les clés sont prioritaires) + * Cursor::dyn permet d'insérer une valeur qui sera évaluée plus tard lors de la résolution du contenu * pour Cursor, CTable, etc., un paramètre "params_func" permet de générer une From f413400c4065459e12b06016e35ea8c1c066bb13 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 30 Jun 2025 10:24:43 +0400 Subject: [PATCH 05/10] modifs.mineures sans commentaires --- src/schema/TODO.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/schema/TODO.md b/src/schema/TODO.md index 1f5cda4..c318ab2 100644 --- a/src/schema/TODO.md +++ b/src/schema/TODO.md @@ -21,6 +21,33 @@ * `ScalarSchema::from_property()` +* pour le support des propriétés des objets, il faudrait pouvoir spécifier + comment instancier l'objet. je ne sais pas si ça doit se mettre au niveau du + type, du schéma, ou autre + ~~~php + Schema::ns($schema, [ + "rt" => ["?string", "required" => true], + "rtd" => ["?int", "required" => true, "default" => 42], + "ot" => ["?int"], + "otd" => ["?string", "default" => "hello"], + "ot2" => ["int"], + "" => ["assoc", + "class" => MyClass::class, + ], + ]); + + # peut provisionner la classe suivante + class MyClass { + public ?string $rt; + public ?int $rtd = 42; + public ?int $ot = null; + public ?string $otd = "hello"; + public int $ot2 = 0; + } + ~~~ + il y a potentiellement un problème d'oeuf et de poule si on se sert de ce + genre de définitions pour autogénérer la classe + * l'argument $format de AssocWrapper::format() est un tableau associatif `[$key => $format]` cela permet de spécifier des format spécifiques pour certains champs. From df13496a86e2baff12b134e818624a415d0eb210 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 30 Jun 2025 12:56:56 +0400 Subject: [PATCH 06/10] modifs.mineures sans commentaires --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index a085552..0637136 100644 --- a/TODO.md +++ b/TODO.md @@ -117,7 +117,8 @@ $element = ["func" => [My::class, "get_element"]]; ~~~ func n'est considéré que si $element était un tableau à la base - s'il y a d'autres clés, elles sont fusionnées avec le résultat de $func() (dont les clés sont prioritaires) + s'il y a d'autres clés, elles sont fusionnées avec le résultat de $func() + (dont les clés sont prioritaires) * Cursor::dyn permet d'insérer une valeur qui sera évaluée plus tard lors de la résolution du contenu From 79b8c33d4f5b4cff02c78bb8d5e6a39927893bb8 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 1 Jul 2025 17:53:52 +0400 Subject: [PATCH 07/10] modifs.mineures sans commentaires --- src/cache/CacheChannel.php | 173 ++++++++++++++++-------------- src/cache/CacheDataChannel.php | 53 --------- src/cache/CacheFile.php | 6 +- src/cache/TODO.md | 8 ++ src/cache/cache.php | 37 +++---- tests/cache/.gitignore | 2 + tests/cache/CursorChannelTest.php | 22 ++++ tests/cache/_TestCase.php | 23 ++++ tests/cache/cacheTest.php | 30 ++++++ 9 files changed, 192 insertions(+), 162 deletions(-) delete mode 100644 src/cache/CacheDataChannel.php create mode 100644 src/cache/TODO.md create mode 100644 tests/cache/.gitignore create mode 100644 tests/cache/CursorChannelTest.php create mode 100644 tests/cache/_TestCase.php create mode 100644 tests/cache/cacheTest.php diff --git a/src/cache/CacheChannel.php b/src/cache/CacheChannel.php index 4272a99..49f7dce 100644 --- a/src/cache/CacheChannel.php +++ b/src/cache/CacheChannel.php @@ -1,116 +1,127 @@ "varchar(64) not null", - "id" => "varchar(64) not null", - "date_start" => "datetime", - "duration_" => "text", - "primary key (group_id, id)", + "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)", ]; - static function get_cache_ids($id): array { - if (is_array($id)) { - $keys = array_keys($id); - if (array_key_exists("group_id", $id)) $groupIdKey = "group_id"; + static function with(CapacitorStorage $storage, ?iterable $rows=null, $cursorId=null): self { + $channel = (new static($cursorId))->initStorage($storage); + if ($rows !== null) $channel->build($rows); + return $channel; + } + + static function verifix_id(&$cursorId): void { + $cursorId ??= utils::uuidgen(); + if (is_array($cursorId)) { + $keys = array_keys($cursorId); + if (array_key_exists("group_id", $cursorId)) $groupIdKey = "group_id"; else $groupIdKey = $keys[1] ?? null; - $groupId = $id[$groupIdKey] ?? ""; - if (array_key_exists("id", $id)) $idKey = "id"; + $groupId = strval($cursorId[$groupIdKey] ?? ""); + if (array_key_exists("id", $cursorId)) $idKey = "id"; else $idKey = $keys[0] ?? null; - $id = $id[$idKey] ?? ""; + $id = strval($cursorId[$idKey] ?? ""); } else { $groupId = ""; + $id = strval($cursorId); } - if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) { - # si le groupe est une classe, faire un hash du package pour limiter la - # longueur du groupe - [$package, $groupId] = [$ms[1], $ms[2]]; - $package = substr(md5($package), 0, 4); - $groupId = "${groupId}_$package"; + # si le groupe ou le nom sont trop grand, en faire un hash + if (strlen($groupId) > 32) { + $groupId = md5($groupId); } - return ["group_id" => $groupId, "id" => $id]; + if (strlen($id) > 128) { + $id = substr($id, 0, 128 - 32).md5($id); + } + $cursorId = ["group_id" => $groupId, "id" => $id]; } - function __construct(?string $duration=null, ?string $name=null) { - parent::__construct($name); - $this->duration = $duration ?? static::DURATION; - $this->includes = static::INCLUDES; - $this->excludes = static::EXCLUDES; + /** + * @param array|string $cursorId + */ + function __construct($cursorId) { + parent::__construct(); + self::verifix_id($cursorId); + $this->cursorId = $cursorId; } - protected string $duration; + protected array $cursorId; - protected ?array $includes; + function getCursorId(): array { + return $this->cursorId; + } - protected ?array $excludes; + function getBaseFilter(): ?array { + return $this->cursorId; + } - function getItemValues($item): ?array { - return cl::merge(self::get_cache_ids($item), [ - "item" => null, + 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); + return cl::merge($this->cursorId, [ + "key_index" => $index, + "key" => $key, + "search" => $this->getSearch($item), ]); } - function onCreate($item, array $row, ?array $alwaysNull, ?string $duration=null): ?array { - $now = new DateTime(); - $duration ??= $this->duration; - return [ - "date_start" => $now, - "duration" => new Delay($duration, $now), - ]; + function reset(bool $recreate=false): void { + $this->index = 0; + parent::reset($recreate); } - function onUpdate($item, array $row, array $prow, ?string $duration=null): ?array { - $now = new DateTime(); - $duration ??= $this->duration; - return [ - "date_start" => $now, - "duration" => new Delay($duration, $now), - ]; - } - - function shouldUpdate($id, bool $noCache=false): bool { - if ($noCache) return true; - - $cacheIds = self::get_cache_ids($id); - $groupId = $cacheIds["group_id"]; - if ($groupId) { - $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; + function chargeAll(?iterable $items, $func=null, ?array $args=null): int { + $this->index = 0; + 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]); } - - $found = false; - $expired = false; - $this->each($cacheIds, - function($row) use (&$found, &$expired) { - $found = true; - $expired = $row["duration"]->isElapsed(); - }); - return !$found || $expired; + return $count; } - function setCached($id, ?string $duration=null): void { - $cacheIds = self::get_cache_ids($id); - $this->charge($cacheIds, null, [$duration]); + function build(?iterable $items): self { + $this->reset(true); + $this->chargeAll($items); + return $this; } - function resetCached($id) { - $cacheIds = self::get_cache_ids($id); - $this->delete($cacheIds); + 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; + } } } diff --git a/src/cache/CacheDataChannel.php b/src/cache/CacheDataChannel.php deleted file mode 100644 index af885d6..0000000 --- a/src/cache/CacheDataChannel.php +++ /dev/null @@ -1,53 +0,0 @@ - "varchar(128) primary key not null", - "all_values" => "mediumtext", - ]; - - function __construct($id, callable $builder, ?string $duration=null) { - $this->cacheIds = $cacheIds = CacheChannel::get_cache_ids($id); - $this->builder = Closure::fromCallable($builder); - $this->duration = $duration; - $name = "{$cacheIds["group_id"]}-{$cacheIds["id"]}"; - parent::__construct($name); - } - - protected array $cacheIds; - - protected Closure $builder; - - protected ?string $duration = null; - - function getItemValues($item): ?array { - $key = array_keys($item)[0]; - $row = $item[$key]; - return [ - "key" => $key, - "item" => $row, - "all_values" => implode(" ", cl::filter_n(cl::with($row))), - ]; - } - - function getIterator(): Traversable { - $cm = cache::get(); - if ($cm->shouldUpdate($this->cacheIds)) { - $this->capacitor->reset(); - foreach (($this->builder)() as $key => $row) { - $this->charge([$key => $row]); - } - $cm->setCached($this->cacheIds, $this->duration); - } - return $this->discharge(false); - } -} diff --git a/src/cache/CacheFile.php b/src/cache/CacheFile.php index 18cc295..6cab2ba 100644 --- a/src/cache/CacheFile.php +++ b/src/cache/CacheFile.php @@ -3,6 +3,7 @@ namespace nulib\cache; use Exception; use nulib\cv; +use nulib\ext\utils; use nulib\file; use nulib\file\SharedFile; use nulib\os\path; @@ -26,10 +27,7 @@ class CacheFile extends SharedFile { } function __construct($file, $data=null, ?array $params=null) { - if ($file === null) { - $rand = bin2hex(random_bytes(8)); - $file = path::join(sys_get_temp_dir(), $rand); - } + $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); diff --git a/src/cache/TODO.md b/src/cache/TODO.md new file mode 100644 index 0000000..4356aa0 --- /dev/null +++ b/src/cache/TODO.md @@ -0,0 +1,8 @@ +# nulib\cache + +* CacheChannel + * spécifier des clés supplémentaires utilisable dans la recherche +* CacheFile + * pour $data===null, stocker dans le fichier de cache directement + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/src/cache/cache.php b/src/cache/cache.php index 46f93b0..fce93da 100644 --- a/src/cache/cache.php +++ b/src/cache/cache.php @@ -2,11 +2,9 @@ namespace nulib\cache; use nulib\app; -use nulib\cache\CacheChannel; -use nulib\cache\CacheDataChannel; -use nulib\db\Capacitor; use nulib\db\CapacitorStorage; use nulib\db\sqlite\SqliteStorage; +use nulib\php\func; class cache { protected static ?string $dbfile = null; @@ -17,30 +15,21 @@ class cache { protected static ?CapacitorStorage $storage = null; + protected static function storage(): CapacitorStorage { + return self::$storage ??= new SqliteStorage(self::dbfile()); + } + static function set_storage(CapacitorStorage $storage): CapacitorStorage { return self::$storage = $storage; } - protected static function get_storage(): CapacitorStorage { - return self::$storage ??= new SqliteStorage(self::dbfile()); - } - - protected static ?CacheChannel $channel = null; - - static function set(?CacheChannel $channel): CacheChannel { - $channel ??= new CacheChannel(); - new Capacitor(self::get_storage(), $channel); - return self::$channel = $channel; - } - - static function get(): CacheChannel { - if (self::$channel !== null) return self::$channel; - else return self::set(null); - } - - static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel { - $channel ??= new CacheDataChannel($id, $builder); - new Capacitor(self::get_storage(), $channel); - return $channel; + static function all(?iterable $rows, $cursorId=null): iterable { + CacheChannel::verifix_id($cursorId); + $file = "row-{$cursorId["group_id"]}-{$cursorId["id"]}"; + $cache = new CacheFile($file, function() use ($rows, $cursorId) { + CacheChannel::with(self::storage(), null, $cursorId)->build($rows); + return $cursorId; + }); + return CacheChannel::with(self::storage(), null, $cache->get()); } } diff --git a/tests/cache/.gitignore b/tests/cache/.gitignore new file mode 100644 index 0000000..f59c9dc --- /dev/null +++ b/tests/cache/.gitignore @@ -0,0 +1,2 @@ +/capacitor.db* +/*.cache diff --git a/tests/cache/CursorChannelTest.php b/tests/cache/CursorChannelTest.php new file mode 100644 index 0000000..4dc498f --- /dev/null +++ b/tests/cache/CursorChannelTest.php @@ -0,0 +1,22 @@ + ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + + $channel = CacheChannel::with(self::$storage, $data, "numbers"); + $count = 0; + foreach ($channel as $key => $item) { + msg::info("one: $key => {$item["a"]}"); + $count++; + } + self::assertSame(3, $count); + } +} diff --git a/tests/cache/_TestCase.php b/tests/cache/_TestCase.php new file mode 100644 index 0000000..1a4659e --- /dev/null +++ b/tests/cache/_TestCase.php @@ -0,0 +1,23 @@ +close(); + } +} diff --git a/tests/cache/cacheTest.php b/tests/cache/cacheTest.php new file mode 100644 index 0000000..0f8dbdb --- /dev/null +++ b/tests/cache/cacheTest.php @@ -0,0 +1,30 @@ + ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + foreach ($data as $key => $item) { + msg::info("yield $key"); + yield $key => $item; + sleep(2); + } + msg::info("fin gendata"); + } + + function testUsage() { + $data = cache::all($this->gendata(),"gendata"); + $count = 0; + foreach ($data as $key => $item) { + msg::info("got $key => ".var_export($item, true)); + $count++; + } + self::assertSame(3, $count); + } +} From 8612a9dae4e4603688a3a3f8fd18fdd1417dda21 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 2 Jul 2025 08:23:32 +0400 Subject: [PATCH 08/10] modifs.mineures sans commentaires --- src/cache/CacheFile.php | 108 +++++++++++++++++++++++++++------------- src/cache/TODO.md | 2 - 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/cache/CacheFile.php b/src/cache/CacheFile.php index 6cab2ba..755d91a 100644 --- a/src/cache/CacheFile.php +++ b/src/cache/CacheFile.php @@ -110,6 +110,8 @@ class CacheFile extends SharedFile { protected ?Delay $duration; + protected $data; + protected ?array $datafilenames; /** charger les données. le fichier a déjà été verrouillé en lecture */ @@ -119,6 +121,7 @@ class CacheFile extends SharedFile { [ "start" => $start, "duration" => $duration, + "data" => $data, "datafiles" => $datafilenames, ] = $this->unserialize(null, false, true); if ($this->overrideDuration) { @@ -128,10 +131,12 @@ class CacheFile extends SharedFile { } else { $start = null; $duration = null; + $data = null; $datafilenames = []; } $this->start = $start; $this->duration = $duration; + $this->data = $data; $this->datafilenames = $datafilenames; } @@ -161,15 +166,16 @@ class CacheFile extends SharedFile { $this->serialize([ "start" => $this->start, "duration" => $this->duration, + "data" => $this->data, "datafiles" => $datafilenames, ], false, true); } - protected function loadData(string $datafile) { + protected function loadDatafile(string $datafile) { return file::reader($datafile)->unserialize(); } - protected function saveData(string $datafile, $data): void { + protected function saveDatafile(string $datafile, $data): void { file::writer($datafile)->serialize($data); } @@ -180,10 +186,10 @@ class CacheFile extends SharedFile { } protected function unlinkFiles(bool $datafilesOnly=false): void { - if (!$datafilesOnly) @unlink($this->file); foreach ($this->datafilenames as $datafilename) { $this->unlinkDatafile($datafilename); } + if (!$datafilesOnly) @unlink($this->file); } /** tester si $value peut être mis en cache */ @@ -195,26 +201,28 @@ class CacheFile extends SharedFile { protected ?Delay $oduration; + 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() { - $start = $this->start; + $modified = false; + if ($this->start != $this->ostart) $modified = true; $duration = $this->duration; $oduration = $this->oduration; - $datafilenames = $this->datafilenames; - $modified = false; - if ($start != $this->ostart) $modified = true; if ($duration === null || $oduration === null) $modified = true; elseif ($duration->getDest() != $oduration->getDest()) $modified = true; - # égalité stricte uniquement pour $datafiles qui est un array - if ($datafilenames !== $this->odatafilenames) $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(); @@ -230,8 +238,13 @@ class CacheFile extends SharedFile { $this->afterAction(); return $result; } finally { + $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); } @@ -242,13 +255,15 @@ 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); $updateMetadata = $this->shouldUpdate($noCache); - $updateData = !array_key_exists($datafilename, $this->datafilenames); + if ($defaultSource) $updateData = $this->data === null; + else $updateData = !array_key_exists($datafilename, $this->datafilenames); if (!$this->readonly && ($updateMetadata || $updateData)) { $this->lockWrite(); if ($updateMetadata) { @@ -256,31 +271,52 @@ class CacheFile extends SharedFile { $this->unlinkFiles(true); $this->start = null; $this->duration = null; + $this->data = null; $updateData = true; } - if ($updateData) { - # calculer un fichier - try { - $data = $source->get(); - } catch (Exception $e) { - # ne pas garder le fichier en cas d'exception - $this->unlinkDatafile($datafile); - throw $e; + if ($defaultSource) { + if ($updateData) { + # calculer la valeur + try { + $data = $source->get(); + } 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; } - } elseif (file_exists($datafile)) { - $data = $this->loadData($datafile); + if ($this->shouldCache($data)) $this->data = $data; + else $this->data = $data = null; } else { - $data = null; - } - if ($this->shouldCache($data)) { - $this->saveData($datafile, $data); - $this->datafilenames[$datafilename] = true; - } else { - # ne pas garder le fichier s'il ne faut pas mettre en cache - $this->unlinkDatafile($datafile); + if ($updateData) { + # calculer la valeur + try { + $data = $source->get(); + } catch (Exception $e) { + # ne pas garder le fichier destination en cas d'exception + $this->unlinkDatafile($datafile); + throw $e; + } + } elseif (file_exists($datafile)) { + $data = $this->loadDatafile($datafile); + } else { + $data = null; + } + if ($this->shouldCache($data)) { + $this->saveDatafile($datafile, $data); + $this->datafilenames[$datafilename] = true; + } else { + # ne pas garder le fichier s'il ne faut pas mettre en cache + $this->unlinkDatafile($datafile); + } } + } elseif ($defaultSource) { + $data = $this->data; } elseif (file_exists($datafile)) { - $data = $this->loadData($datafile); + $data = $this->loadDatafile($datafile); } else { $data = null; } @@ -293,12 +329,14 @@ class CacheFile extends SharedFile { * cette méthode est un NOP */ function refresh(bool $noCache=false): self { - $source = $this->source; - if ($source !== null) $datas = [null]; - else $datas = array_keys($this->sources); - foreach ($datas as $data) { - $this->refreshData($data, $noCache); - } + $this->action(function() use ($noCache) { + $source = $this->source; + if ($source !== null) $datas = [null]; + else $datas = array_keys($this->sources); + foreach ($datas as $data) { + $this->refreshData($data, $noCache); + } + }); return $this; } diff --git a/src/cache/TODO.md b/src/cache/TODO.md index 4356aa0..4f8f006 100644 --- a/src/cache/TODO.md +++ b/src/cache/TODO.md @@ -2,7 +2,5 @@ * CacheChannel * spécifier des clés supplémentaires utilisable dans la recherche -* CacheFile - * pour $data===null, stocker dans le fichier de cache directement -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file From 8079d111cc2db824cc9cf90e861951b855d03c4b Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 2 Jul 2025 10:05:14 +0400 Subject: [PATCH 09/10] modifs.mineures sans commentaires --- src/cache/CacheChannel.php | 29 ++----------- src/cache/CacheFile.php | 7 +++- src/cache/CacheManager.php | 68 +++++++++++++++++++++++++++++++ src/cache/cache.php | 61 +++++++++++++++++++++++---- tests/cache/.gitignore | 2 +- tests/cache/CursorChannelTest.php | 2 +- tests/cache/_TestCase.php | 2 +- tests/cache/cacheTest.php | 45 +++++++++++++++----- 8 files changed, 169 insertions(+), 47 deletions(-) create mode 100644 src/cache/CacheManager.php diff --git a/src/cache/CacheChannel.php b/src/cache/CacheChannel.php index 49f7dce..91f021d 100644 --- a/src/cache/CacheChannel.php +++ b/src/cache/CacheChannel.php @@ -23,42 +23,19 @@ class CacheChannel extends CapacitorChannel implements IteratorAggregate { "primary key (group_id, id, key_index)", ]; - static function with(CapacitorStorage $storage, ?iterable $rows=null, $cursorId=null): self { + static function with(?iterable $rows=null, $cursorId=null, ?CapacitorStorage $storage=null): self { + $storage ??= cache::storage(); $channel = (new static($cursorId))->initStorage($storage); if ($rows !== null) $channel->build($rows); return $channel; } - static function verifix_id(&$cursorId): void { - $cursorId ??= utils::uuidgen(); - if (is_array($cursorId)) { - $keys = array_keys($cursorId); - if (array_key_exists("group_id", $cursorId)) $groupIdKey = "group_id"; - else $groupIdKey = $keys[1] ?? null; - $groupId = strval($cursorId[$groupIdKey] ?? ""); - if (array_key_exists("id", $cursorId)) $idKey = "id"; - else $idKey = $keys[0] ?? null; - $id = strval($cursorId[$idKey] ?? ""); - } else { - $groupId = ""; - $id = strval($cursorId); - } - # 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); - } - $cursorId = ["group_id" => $groupId, "id" => $id]; - } - /** * @param array|string $cursorId */ function __construct($cursorId) { parent::__construct(); - self::verifix_id($cursorId); + cache::verifix_id($cursorId); $this->cursorId = $cursorId; } diff --git a/src/cache/CacheFile.php b/src/cache/CacheFile.php index 755d91a..a4684f7 100644 --- a/src/cache/CacheFile.php +++ b/src/cache/CacheFile.php @@ -19,6 +19,11 @@ class CacheFile extends SharedFile { 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(); @@ -26,7 +31,7 @@ class CacheFile extends SharedFile { throw ValueException::invalid_type($source, CacheData::class); } - function __construct($file, $data=null, ?array $params=null) { + 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); diff --git a/src/cache/CacheManager.php b/src/cache/CacheManager.php new file mode 100644 index 0000000..2b134c9 --- /dev/null +++ b/src/cache/CacheManager.php @@ -0,0 +1,68 @@ +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; + } +} diff --git a/src/cache/cache.php b/src/cache/cache.php index fce93da..fa0991f 100644 --- a/src/cache/cache.php +++ b/src/cache/cache.php @@ -4,6 +4,7 @@ namespace nulib\cache; use nulib\app; use nulib\db\CapacitorStorage; use nulib\db\sqlite\SqliteStorage; +use nulib\ext\utils; use nulib\php\func; class cache { @@ -15,7 +16,7 @@ class cache { protected static ?CapacitorStorage $storage = null; - protected static function storage(): CapacitorStorage { + static function storage(): CapacitorStorage { return self::$storage ??= new SqliteStorage(self::dbfile()); } @@ -23,13 +24,59 @@ class cache { return self::$storage = $storage; } - static function all(?iterable $rows, $cursorId=null): iterable { - CacheChannel::verifix_id($cursorId); - $file = "row-{$cursorId["group_id"]}-{$cursorId["id"]}"; - $cache = new CacheFile($file, function() use ($rows, $cursorId) { - CacheChannel::with(self::storage(), null, $cursorId)->build($rows); + 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]; + } + + static function get(callable $compute, $dataId=null, ?string $file=null) { + self::verifix_id($dataId); + $file ??= "{$dataId["group_id"]}-{$dataId["id"]}"; + $noCache = !self::should_cache($dataId["id"], $dataId["group_id"]); + return CacheFile::with($compute, $file)->get(null, $noCache); + } + + static function all(?iterable $rows, $cursorId=null, ?string $file=null): iterable { + self::verifix_id($cursorId); + $file ??= "{$cursorId["group_id"]}-{$cursorId["id"]}--rows"; + $ccursorId = new CacheFile($file, function() use ($rows, $cursorId) { + CacheChannel::with(null, $cursorId)->build($rows); return $cursorId; }); - return CacheChannel::with(self::storage(), null, $cache->get()); + $noCache = !self::should_cache($cursorId["id"], $cursorId["group_id"]); + return CacheChannel::with(null, $ccursorId->get(null, $noCache)); } } diff --git a/tests/cache/.gitignore b/tests/cache/.gitignore index f59c9dc..d540dae 100644 --- a/tests/cache/.gitignore +++ b/tests/cache/.gitignore @@ -1,2 +1,2 @@ -/capacitor.db* +/*.db* /*.cache diff --git a/tests/cache/CursorChannelTest.php b/tests/cache/CursorChannelTest.php index 4dc498f..6a1aeee 100644 --- a/tests/cache/CursorChannelTest.php +++ b/tests/cache/CursorChannelTest.php @@ -11,7 +11,7 @@ class CursorChannelTest extends _TestCase { ["a" => 1, "b" => 2], ]; - $channel = CacheChannel::with(self::$storage, $data, "numbers"); + $channel = CacheChannel::with($data, "numbers", self::$storage); $count = 0; foreach ($channel as $key => $item) { msg::info("one: $key => {$item["a"]}"); diff --git a/tests/cache/_TestCase.php b/tests/cache/_TestCase.php index 1a4659e..f05875c 100644 --- a/tests/cache/_TestCase.php +++ b/tests/cache/_TestCase.php @@ -12,7 +12,7 @@ class _TestCase extends TestCase { static function setUpBeforeClass(): void { parent::setUpBeforeClass(); msg::set_messenger_class(StdMessenger::class); - self::$storage = new SqliteStorage(__DIR__."/capacitor.db"); + self::$storage = new SqliteStorage(__DIR__."/cache.db"); cache::set_storage(self::$storage); } diff --git a/tests/cache/cacheTest.php b/tests/cache/cacheTest.php index 0f8dbdb..9d24daa 100644 --- a/tests/cache/cacheTest.php +++ b/tests/cache/cacheTest.php @@ -1,16 +1,18 @@ ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + function gendata() { - $data = [ - "fr" => ["a" => "un", "b" => "deux"], - "eng" => ["a" => "one", "b" => "two"], - ["a" => 1, "b" => 2], - ]; - foreach ($data as $key => $item) { + foreach (self::DATA as $key => $item) { msg::info("yield $key"); yield $key => $item; sleep(2); @@ -18,13 +20,36 @@ class cacheTest extends _TestCase { msg::info("fin gendata"); } - function testUsage() { - $data = cache::all($this->gendata(),"gendata"); + function _testRows(iterable $rows) { $count = 0; - foreach ($data as $key => $item) { - msg::info("got $key => ".var_export($item, true)); + foreach ($rows as $key => $row) { + msg::info("got $key => ".var_export($row, true)); $count++; } self::assertSame(3, $count); } + + function testUsage() { + msg::section("all"); + $rows = cache::all($this->gendata(),"gendata"); + $this->_testRows($rows); + + msg::section("get"); + $rows = cache::get(function() { + return self::DATA; + },"gendata"); + $this->_testRows($rows); + } + + function testNc() { + cache::nc(); + + msg::section("first pass"); + $rows = cache::all($this->gendata(),"gendata"); + $this->_testRows($rows); + + msg::section("second pass"); + $rows = cache::all($this->gendata(),"gendata"); + $this->_testRows($rows); + } } From 94d6d5cf3e0925956f213eed5039b098b2d40b71 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 2 Jul 2025 10:52:03 +0400 Subject: [PATCH 10/10] modifs.mineures sans commentaires --- src/cache/CacheChannel.php | 72 ++++++++++++++++++++----------- src/cache/TODO.md | 6 --- src/cache/cache.php | 4 +- tests/.gitignore | 2 + tests/cache/.gitignore | 2 - tests/cache/CacheChannelTest.php | 38 ++++++++++++++++ tests/cache/CursorChannelTest.php | 22 ---------- 7 files changed, 90 insertions(+), 56 deletions(-) delete mode 100644 src/cache/TODO.md create mode 100644 tests/.gitignore delete mode 100644 tests/cache/.gitignore create mode 100644 tests/cache/CacheChannelTest.php delete mode 100644 tests/cache/CursorChannelTest.php diff --git a/src/cache/CacheChannel.php b/src/cache/CacheChannel.php index 91f021d..68fde79 100644 --- a/src/cache/CacheChannel.php +++ b/src/cache/CacheChannel.php @@ -10,19 +10,6 @@ use nulib\php\func; use Traversable; class CacheChannel extends CapacitorChannel implements IteratorAggregate { - const NAME = "cache"; - const TABLE_NAME = "cache"; - - 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)", - ]; - static function with(?iterable $rows=null, $cursorId=null, ?CapacitorStorage $storage=null): self { $storage ??= cache::storage(); $channel = (new static($cursorId))->initStorage($storage); @@ -30,23 +17,53 @@ class CacheChannel extends CapacitorChannel implements IteratorAggregate { return $channel; } + const NAME = "cache"; + const TABLE_NAME = "cache"; + + 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); - $this->cursorId = $cursorId; + [ + "group_id" => $this->groupId, + "id" => $this->id, + ] = $cursorId; } - protected array $cursorId; + protected string $groupId; + + protected string $id; function getCursorId(): array { - return $this->cursorId; + return [ + "group_id" => $this->groupId, + "id" => $this->id, + ]; } function getBaseFilter(): ?array { - return $this->cursorId; + return [ + "group_id_" => $this->groupId, + "id_" => $this->id, + ]; } protected int $index = 0; @@ -61,10 +78,17 @@ class CacheChannel extends CapacitorChannel implements IteratorAggregate { $index = $this->index++; $key = $key ?? $index; $key = substr(strval($key), 0, 128); - return cl::merge($this->cursorId, [ - "key_index" => $index, - "key" => $key, - "search" => $this->getSearch($item), + $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), ]); } @@ -85,18 +109,18 @@ class CacheChannel extends CapacitorChannel implements IteratorAggregate { } function build(?iterable $items): self { - $this->reset(true); + $this->delete(null); $this->chargeAll($items); return $this; } function getIterator(): Traversable { $rows = $this->dbAll([ - "cols" => ["key", "item__"], + "cols" => ["key_", "item__"], "where" => $this->getBaseFilter(), ]); foreach ($rows as $row) { - $key = $row["key"]; + $key = $row["key_"]; $item = $this->unserialize($row["item__"]); yield $key => $item; } diff --git a/src/cache/TODO.md b/src/cache/TODO.md deleted file mode 100644 index 4f8f006..0000000 --- a/src/cache/TODO.md +++ /dev/null @@ -1,6 +0,0 @@ -# nulib\cache - -* CacheChannel - * spécifier des clés supplémentaires utilisable dans la recherche - --*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/src/cache/cache.php b/src/cache/cache.php index fa0991f..b0a25c2 100644 --- a/src/cache/cache.php +++ b/src/cache/cache.php @@ -64,14 +64,14 @@ class cache { static function get(callable $compute, $dataId=null, ?string $file=null) { self::verifix_id($dataId); - $file ??= "{$dataId["group_id"]}-{$dataId["id"]}"; + $file ??= "{$dataId["group_id"]}_{$dataId["id"]}"; $noCache = !self::should_cache($dataId["id"], $dataId["group_id"]); return CacheFile::with($compute, $file)->get(null, $noCache); } static function all(?iterable $rows, $cursorId=null, ?string $file=null): iterable { self::verifix_id($cursorId); - $file ??= "{$cursorId["group_id"]}-{$cursorId["id"]}--rows"; + $file ??= "{$cursorId["group_id"]}_{$cursorId["id"]}_rows"; $ccursorId = new CacheFile($file, function() use ($rows, $cursorId) { CacheChannel::with(null, $cursorId)->build($rows); return $cursorId; diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..69bea44 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +*.db* +*.cache diff --git a/tests/cache/.gitignore b/tests/cache/.gitignore deleted file mode 100644 index d540dae..0000000 --- a/tests/cache/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*.db* -/*.cache diff --git a/tests/cache/CacheChannelTest.php b/tests/cache/CacheChannelTest.php new file mode 100644 index 0000000..2de3842 --- /dev/null +++ b/tests/cache/CacheChannelTest.php @@ -0,0 +1,38 @@ + ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + + function testUsage() { + $channel = CacheChannel::with(self::DATA, "numbers", 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 CacheChannel { + const NAME = "numbersac"; + const TABLE_NAME = self::NAME; + const ADD_COLUMNS = [ + "a" => "varchar(30)", + ]; + })->initStorage(self::$storage)->build(self::DATA); + $count = 0; + foreach ($channel as $key => $item) { + msg::info("one: $key => {$item["a"]}"); + $count++; + } + self::assertSame(3, $count); + } +} diff --git a/tests/cache/CursorChannelTest.php b/tests/cache/CursorChannelTest.php deleted file mode 100644 index 6a1aeee..0000000 --- a/tests/cache/CursorChannelTest.php +++ /dev/null @@ -1,22 +0,0 @@ - ["a" => "un", "b" => "deux"], - "eng" => ["a" => "one", "b" => "two"], - ["a" => 1, "b" => 2], - ]; - - $channel = CacheChannel::with($data, "numbers", self::$storage); - $count = 0; - foreach ($channel as $key => $item) { - msg::info("one: $key => {$item["a"]}"); - $count++; - } - self::assertSame(3, $count); - } -}