<pman>Intégration de la branche dev74

This commit is contained in:
Jephté Clain 2025-07-07 21:33:22 +04:00
commit 019f4333c0
15 changed files with 531 additions and 216 deletions

47
TODO.md
View File

@ -73,6 +73,53 @@
~~~ ~~~
bien entendu, bien que ce ne soit pas démontré ici, le premier argument 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 (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"];
}
~~~
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 * Cursor::dyn permet d'insérer une valeur qui sera évaluée plus tard lors de la
résolution du contenu résolution du contenu
* pour Cursor, CTable, etc., un paramètre "params_func" permet de générer une * pour Cursor, CTable, etc., un paramètre "params_func" permet de générer une

View File

@ -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 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 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: # template:
#static final function xxx($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("xxx", $suffix, $alt); } #static final function xxx($suffix=null, ?string $alt=null): array { return self::manager()->getIcon("xxx", $suffix, $alt); }
} }

View File

@ -1,116 +1,128 @@
<?php <?php
namespace nulib\cache; namespace nulib\cache;
use IteratorAggregate;
use nulib\cl; use nulib\cl;
use nulib\db\CapacitorChannel; use nulib\db\CapacitorChannel;
use nulib\php\time\DateTime; use nulib\db\CapacitorStorage;
use nulib\php\time\Delay; use nulib\ext\utils;
use nulib\php\func;
use Traversable;
class CacheChannel extends CapacitorChannel { class CacheChannel extends CapacitorChannel implements IteratorAggregate {
/** @var int durée de vie par défaut du cache */ static function with(?iterable $rows=null, $cursorId=null, ?CapacitorStorage $storage=null): self {
const DURATION = "1D"; // jusqu'au lendemain $storage ??= cache::storage();
$channel = (new static($cursorId))->initStorage($storage);
if ($rows !== null) $channel->build($rows);
return $channel;
}
const INCLUDES = null; const NAME = "cache";
const TABLE_NAME = "cache";
const EXCLUDES = null;
const COLUMN_DEFINITIONS = [ const COLUMN_DEFINITIONS = [
"group_id" => "varchar(64) not null", "group_id_" => "varchar(32) not null", // groupe de curseur
"id" => "varchar(64) not null", "id_" => "varchar(128) not null", // nom du curseur
"date_start" => "datetime", "key_index_" => "integer not null",
"duration_" => "text", "key_" => "varchar(128) not null",
"primary key (group_id, id)", "search_" => "varchar(255)",
"primary key (group_id_, id_, key_index_)",
]; ];
static function get_cache_ids($id): array { const ADD_COLUMNS = null;
if (is_array($id)) {
$keys = array_keys($id); protected function COLUMN_DEFINITIONS(): ?array {
if (array_key_exists("group_id", $id)) $groupIdKey = "group_id"; return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS);
else $groupIdKey = $keys[1] ?? null;
$groupId = $id[$groupIdKey] ?? "";
if (array_key_exists("id", $id)) $idKey = "id";
else $idKey = $keys[0] ?? null;
$id = $id[$idKey] ?? "";
} else {
$groupId = "";
}
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";
}
return ["group_id" => $groupId, "id" => $id];
} }
function __construct(?string $duration=null, ?string $name=null) { /**
parent::__construct($name); * @param array|string $cursorId
$this->duration = $duration ?? static::DURATION; */
$this->includes = static::INCLUDES; function __construct($cursorId) {
$this->excludes = static::EXCLUDES; parent::__construct();
cache::verifix_id($cursorId);
[
"group_id" => $this->groupId,
"id" => $this->id,
] = $cursorId;
} }
protected string $duration; protected string $groupId;
protected ?array $includes; protected string $id;
protected ?array $excludes; function getCursorId(): array {
return [
"group_id" => $this->groupId,
"id" => $this->id,
];
}
function getItemValues($item): ?array { function getBaseFilter(): ?array {
return cl::merge(self::get_cache_ids($item), [ return [
"item" => null, "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 onCreate($item, array $row, ?array $alwaysNull, ?string $duration=null): ?array { function reset(bool $recreate=false): void {
$now = new DateTime(); $this->index = 0;
$duration ??= $this->duration; parent::reset($recreate);
return [
"date_start" => $now,
"duration" => new Delay($duration, $now),
];
} }
function onUpdate($item, array $row, array $prow, ?string $duration=null): ?array { function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
$now = new DateTime(); $this->index = 0;
$duration ??= $this->duration; if ($items === null) return 0;
return [ $count = 0;
"date_start" => $now, if ($func !== null) $func = func::with($func, $args)->bind($this);
"duration" => new Delay($duration, $now), foreach ($items as $key => $item) {
]; $count += $this->charge($item, $func, [$key]);
}
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;
} }
return $count;
$found = false;
$expired = false;
$this->each($cacheIds,
function($row) use (&$found, &$expired) {
$found = true;
$expired = $row["duration"]->isElapsed();
});
return !$found || $expired;
} }
function setCached($id, ?string $duration=null): void { function build(?iterable $items): self {
$cacheIds = self::get_cache_ids($id); $this->delete(null);
$this->charge($cacheIds, null, [$duration]); $this->chargeAll($items);
return $this;
} }
function resetCached($id) { function getIterator(): Traversable {
$cacheIds = self::get_cache_ids($id); $rows = $this->dbAll([
$this->delete($cacheIds); "cols" => ["key_", "item__"],
"where" => $this->getBaseFilter(),
]);
foreach ($rows as $row) {
$key = $row["key_"];
$item = $this->unserialize($row["item__"]);
yield $key => $item;
}
} }
} }

View File

@ -1,53 +0,0 @@
<?php
namespace nulib\cache;
use Closure;
use IteratorAggregate;
use nulib\cache\CacheChannel;
use nulib\cache\cache;
use nulib\cl;
use nulib\db\CapacitorChannel;
use Traversable;
class CacheDataChannel extends CapacitorChannel implements IteratorAggregate {
const COLUMN_DEFINITIONS = [
"key" => "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);
}
}

View File

@ -3,6 +3,7 @@ namespace nulib\cache;
use Exception; use Exception;
use nulib\cv; use nulib\cv;
use nulib\ext\utils;
use nulib\file; use nulib\file;
use nulib\file\SharedFile; use nulib\file\SharedFile;
use nulib\os\path; use nulib\os\path;
@ -18,6 +19,11 @@ class CacheFile extends SharedFile {
const EXT = ".cache"; 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 { protected function ensure_source($source): CacheData {
if ($source instanceof CacheData) return $source; if ($source instanceof CacheData) return $source;
if (cv::subclass_of($source, CacheData::class)) return new $source(); if (cv::subclass_of($source, CacheData::class)) return new $source();
@ -25,11 +31,8 @@ class CacheFile extends SharedFile {
throw ValueException::invalid_type($source, CacheData::class); throw ValueException::invalid_type($source, CacheData::class);
} }
function __construct($file, ?array $params=null) { function __construct(?string $file, $data=null, ?array $params=null) {
if ($file === null) { $file ??= path::join(sys_get_temp_dir(), utils::uuidgen());
$rand = bin2hex(random_bytes(8));
$file = path::join(sys_get_temp_dir(), $rand);
}
$file = path::ensure_ext($file, self::EXT); $file = path::ensure_ext($file, self::EXT);
$this->basedir = path::dirname($file); $this->basedir = path::dirname($file);
$basename = path::filename($file); $basename = path::filename($file);
@ -38,7 +41,7 @@ class CacheFile extends SharedFile {
$this->overrideDuration = $params["override_duration"] ?? false; $this->overrideDuration = $params["override_duration"] ?? false;
$this->readonly = $params["readonly"] ?? false; $this->readonly = $params["readonly"] ?? false;
$this->cacheNull = $params["cache_null"] ?? false; $this->cacheNull = $params["cache_null"] ?? false;
$data = $params["data"] ?? null; $data ??= $params["data"] ?? null;
$this->sources = null; $this->sources = null;
if ($data === null) { if ($data === null) {
$this->source = null; $this->source = null;
@ -112,6 +115,8 @@ class CacheFile extends SharedFile {
protected ?Delay $duration; protected ?Delay $duration;
protected $data;
protected ?array $datafilenames; protected ?array $datafilenames;
/** charger les données. le fichier a déjà été verrouillé en lecture */ /** charger les données. le fichier a déjà été verrouillé en lecture */
@ -121,6 +126,7 @@ class CacheFile extends SharedFile {
[ [
"start" => $start, "start" => $start,
"duration" => $duration, "duration" => $duration,
"data" => $data,
"datafiles" => $datafilenames, "datafiles" => $datafilenames,
] = $this->unserialize(null, false, true); ] = $this->unserialize(null, false, true);
if ($this->overrideDuration) { if ($this->overrideDuration) {
@ -130,10 +136,12 @@ class CacheFile extends SharedFile {
} else { } else {
$start = null; $start = null;
$duration = null; $duration = null;
$data = null;
$datafilenames = []; $datafilenames = [];
} }
$this->start = $start; $this->start = $start;
$this->duration = $duration; $this->duration = $duration;
$this->data = $data;
$this->datafilenames = $datafilenames; $this->datafilenames = $datafilenames;
} }
@ -163,15 +171,16 @@ class CacheFile extends SharedFile {
$this->serialize([ $this->serialize([
"start" => $this->start, "start" => $this->start,
"duration" => $this->duration, "duration" => $this->duration,
"data" => $this->data,
"datafiles" => $datafilenames, "datafiles" => $datafilenames,
], false, true); ], false, true);
} }
protected function loadData(string $datafile) { protected function loadDatafile(string $datafile) {
return file::reader($datafile)->unserialize(); return file::reader($datafile)->unserialize();
} }
protected function saveData(string $datafile, $data): void { protected function saveDatafile(string $datafile, $data): void {
file::writer($datafile)->serialize($data); file::writer($datafile)->serialize($data);
} }
@ -182,10 +191,10 @@ class CacheFile extends SharedFile {
} }
protected function unlinkFiles(bool $datafilesOnly=false): void { protected function unlinkFiles(bool $datafilesOnly=false): void {
if (!$datafilesOnly) @unlink($this->file);
foreach ($this->datafilenames as $datafilename) { foreach ($this->datafilenames as $datafilename) {
$this->unlinkDatafile($datafilename); $this->unlinkDatafile($datafilename);
} }
if (!$datafilesOnly) @unlink($this->file);
} }
/** tester si $value peut être mis en cache */ /** tester si $value peut être mis en cache */
@ -197,26 +206,28 @@ class CacheFile extends SharedFile {
protected ?Delay $oduration; protected ?Delay $oduration;
protected $odata;
protected ?array $odatafilenames; protected ?array $odatafilenames;
protected function beforeAction() { protected function beforeAction() {
$this->loadMetadata(); $this->loadMetadata();
$this->ostart = cv::clone($this->start); $this->ostart = cv::clone($this->start);
$this->oduration = cv::clone($this->duration); $this->oduration = cv::clone($this->duration);
$this->odata = cv::clone($this->data);
$this->odatafilenames = cv::clone($this->datafilenames); $this->odatafilenames = cv::clone($this->datafilenames);
} }
protected function afterAction() { protected function afterAction() {
$start = $this->start; $modified = false;
if ($this->start != $this->ostart) $modified = true;
$duration = $this->duration; $duration = $this->duration;
$oduration = $this->oduration; $oduration = $this->oduration;
$datafilenames = $this->datafilenames;
$modified = false;
if ($start != $this->ostart) $modified = true;
if ($duration === null || $oduration === null) $modified = true; if ($duration === null || $oduration === null) $modified = true;
elseif ($duration->getDest() != $oduration->getDest()) $modified = true; elseif ($duration->getDest() != $oduration->getDest()) $modified = true;
# égalité stricte uniquement pour $datafiles qui est un array # égalité stricte uniquement pour $data et $datafiles
if ($datafilenames !== $this->odatafilenames) $modified = true; if ($this->data !== $this->odata) $modified = true;
if ($this->datafilenames !== $this->odatafilenames) $modified = true;
if ($modified && !$this->readonly) { if ($modified && !$this->readonly) {
$this->lockWrite(); $this->lockWrite();
$this->saveMetadata(); $this->saveMetadata();
@ -232,8 +243,13 @@ class CacheFile extends SharedFile {
$this->afterAction(); $this->afterAction();
return $result; return $result;
} finally { } finally {
$this->ostart = null;
$this->oduration = null;
$this->odata = null;
$this->odatafilenames = null;
$this->start = null; $this->start = null;
$this->duration = null; $this->duration = null;
$this->data = null;
$this->datafilenames = null; $this->datafilenames = null;
$this->unlock(true); $this->unlock(true);
} }
@ -243,51 +259,95 @@ class CacheFile extends SharedFile {
return null; return null;
} }
function get($data=null, bool $noCache=false) { protected function refreshData($data, bool $noCache) {
return $this->action(function () use ($data, $noCache) { $defaultSource = $data === null;
$source = $this->getSource($data); $source = $this->getSource($data);
$dataname = $source->getName(); $dataname = $source->getName();
$datafilename = ".{$this->basename}.{$dataname}".self::EXT; $datafilename = ".{$this->basename}.{$dataname}".self::EXT;
$datafile = path::join($this->basedir, $datafilename); $datafile = path::join($this->basedir, $datafilename);
$updateMetadata = $this->shouldUpdate($noCache); $updateMetadata = $this->shouldUpdate($noCache);
$updateData = !array_key_exists($datafilename, $this->datafilenames); if ($defaultSource) $updateData = $this->data === null;
if (!$this->readonly && ($updateMetadata || $updateData)) { else $updateData = !array_key_exists($datafilename, $this->datafilenames);
$this->lockWrite(); if (!$this->readonly && ($updateMetadata || $updateData)) {
if ($updateMetadata) { $this->lockWrite();
# il faut refaire tout le cache if ($updateMetadata) {
$this->unlinkFiles(true); # il faut refaire tout le cache
$this->start = null; $this->unlinkFiles(true);
$this->duration = null; $this->start = null;
$updateData = true; $this->duration = null;
} $this->data = null;
$updateData = true;
}
if ($defaultSource) {
if ($updateData) { if ($updateData) {
# calculer un fichier # calculer la valeur
try { try {
$data = $source->get(); $data = $source->get();
} catch (Exception $e) { } catch (Exception $e) {
# ne pas garder le fichier en cas d'exception # 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->get();
} catch (Exception $e) {
# ne pas garder le fichier destination en cas d'exception
$this->unlinkDatafile($datafile); $this->unlinkDatafile($datafile);
throw $e; throw $e;
} }
} elseif (file_exists($datafile)) { } elseif (file_exists($datafile)) {
$data = $this->loadData($datafile); $data = $this->loadDatafile($datafile);
} else { } else {
$data = null; $data = null;
} }
if ($this->shouldCache($data)) { if ($this->shouldCache($data)) {
$this->saveData($datafile, $data); $this->saveDatafile($datafile, $data);
$this->datafilenames[$datafilename] = true; $this->datafilenames[$datafilename] = true;
} else { } else {
# ne pas garder le fichier s'il ne faut pas mettre en cache # ne pas garder le fichier s'il ne faut pas mettre en cache
$this->unlinkDatafile($datafile); $this->unlinkDatafile($datafile);
} }
} elseif (file_exists($datafile)) {
$data = $this->loadData($datafile);
} else {
$data = null;
} }
return $data; } elseif ($defaultSource) {
$data = $this->data;
} elseif (file_exists($datafile)) {
$data = $this->loadDatafile($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 {
$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;
}
function get($data=null, bool $noCache=false) {
return $this->action(function () use ($data, $noCache) {
return $this->refreshData($data, $noCache);
}); });
} }

68
src/cache/CacheManager.php vendored Normal file
View 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;
}
}

72
src/cache/cache.php vendored
View File

@ -2,11 +2,10 @@
namespace nulib\cache; namespace nulib\cache;
use nulib\app; use nulib\app;
use nulib\cache\CacheChannel;
use nulib\cache\CacheDataChannel;
use nulib\db\Capacitor;
use nulib\db\CapacitorStorage; use nulib\db\CapacitorStorage;
use nulib\db\sqlite\SqliteStorage; use nulib\db\sqlite\SqliteStorage;
use nulib\ext\utils;
use nulib\php\func;
class cache { class cache {
protected static ?string $dbfile = null; protected static ?string $dbfile = null;
@ -17,30 +16,67 @@ class cache {
protected static ?CapacitorStorage $storage = null; protected static ?CapacitorStorage $storage = null;
static function storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
}
static function set_storage(CapacitorStorage $storage): CapacitorStorage { static function set_storage(CapacitorStorage $storage): CapacitorStorage {
return self::$storage = $storage; return self::$storage = $storage;
} }
protected static function get_storage(): CapacitorStorage { protected static ?CacheManager $manager = null;
return self::$storage ??= new SqliteStorage(self::dbfile());
static function manager(): CacheManager {
return self::$manager ??= new CacheManager();
} }
protected static ?CacheChannel $channel = null; static function set_manager(CacheManager $manager): CacheManager {
return self::$manager = $manager;
static function set(?CacheChannel $channel): CacheChannel {
$channel ??= new CacheChannel();
new Capacitor(self::get_storage(), $channel);
return self::$channel = $channel;
} }
static function get(): CacheChannel { static function nc(bool $noCache=true, bool $reset=false): void {
if (self::$channel !== null) return self::$channel; self::manager()->setNoCache($noCache, $reset);
else return self::set(null);
} }
static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel { protected static function should_cache(string $id, ?string $groupId=null, bool $reset=true): bool {
$channel ??= new CacheDataChannel($id, $builder); return self::manager()->shouldCache($id, $groupId, $reset);
new Capacitor(self::get_storage(), $channel); }
return $channel;
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;
});
$noCache = !self::should_cache($cursorId["id"], $cursorId["group_id"]);
return CacheChannel::with(null, $ccursorId->get(null, $noCache));
} }
} }

View File

@ -98,7 +98,7 @@ abstract class AbstractStorageApp extends Application {
} }
} }
$first = true; $first = true;
$capacitor->each($filter, function ($item, $row) use (&$first) { $capacitor->each($filter, function ($row) use (&$first) {
if ($first) $first = false; if ($first) $first = false;
else echo "---\n"; else echo "---\n";
yaml::dump($row); yaml::dump($row);

View File

@ -87,7 +87,7 @@ class NucacheApp extends Application {
switch ($this->action) { switch ($this->action) {
case self::ACTION_READ: case self::ACTION_READ:
if ($showSection) msg::section($file); if ($showSection) msg::section($file);
$cache = new CacheFile($file, [ $cache = new CacheFile($file, null, [
"readonly" => true, "readonly" => true,
"duration" => "INF", "duration" => "INF",
"override_duration" => true, "override_duration" => true,
@ -96,7 +96,7 @@ class NucacheApp extends Application {
break; break;
case self::ACTION_INFOS: case self::ACTION_INFOS:
if ($showSection) msg::section($file); if ($showSection) msg::section($file);
$cache = new CacheFile($file, [ $cache = new CacheFile($file, null, [
"readonly" => true, "readonly" => true,
]); ]);
yaml::dump($cache->getInfos()); yaml::dump($cache->getInfos());

View File

@ -21,6 +21,33 @@
* `ScalarSchema::from_property()` * `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 * l'argument $format de AssocWrapper::format() est un tableau associatif
`[$key => $format]` `[$key => $format]`
cela permet de spécifier des format spécifiques pour certains champs. cela permet de spécifier des format spécifiques pour certains champs.

View File

@ -17,22 +17,22 @@ function show(string $prefix, CacheFile $cache, bool $dumpInfos=true): void {
//system("rm -f *.cache .*.cache"); //system("rm -f *.cache .*.cache");
$what = [ $what = [
//"null", "null",
"one", "one",
//"two", "two",
//"three", "three",
]; ];
$duration = 10; $duration = 10;
if (in_array("null", $what)) { if (in_array("null", $what)) {
$null = new CacheFile("null", [ $null = new CacheFile("null", null, [
"duration" => $duration, "duration" => $duration,
]); ]);
show("null", $null); show("null", $null);
} }
if (in_array("one", $what)) { if (in_array("one", $what)) {
$one = new class("one", [ $one = new class("one", null, [
"duration" => $duration, "duration" => $duration,
]) extends CacheFile { ]) extends CacheFile {
protected function compute() { protected function compute() {
@ -43,11 +43,10 @@ if (in_array("one", $what)) {
} }
if (in_array("two", $what)) { if (in_array("two", $what)) {
$two = new CacheFile("two", [ $two = new CacheFile("two", new CacheData(function () {
return 2;
}), [
"duration" => $duration, "duration" => $duration,
"data" => new CacheData(function () {
return 2;
}),
]); ]);
show("two", $two); show("two", $two);
} }
@ -62,12 +61,10 @@ if (in_array("three", $what)) {
}); });
$three = new CacheFile("three", [ $three = new CacheFile("three", [
"data" => [ "data31" => $data31,
"data31" => $data31, $data31, # name=data31name
$data31, # name=data31name "data32" => $data32,
"data32" => $data32, $data32, # name=""
$data32, # name=""
],
]); ]);
Txx("three.0=", $three->get("data31")); Txx("three.0=", $three->get("data31"));
Txx("three.1=", $three->get("data31name")); Txx("three.1=", $three->get("data31name"));

2
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.db*
*.cache

38
tests/cache/CacheChannelTest.php vendored Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace nulib\cache;
use nulib\output\msg;
class CacheChannelTest extends _TestCase {
const DATA = [
"fr" => ["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);
}
}

23
tests/cache/_TestCase.php vendored Normal file
View 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();
}
}

55
tests/cache/cacheTest.php vendored Normal file
View File

@ -0,0 +1,55 @@
<?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() {
foreach (self::DATA as $key => $item) {
msg::info("yield $key");
yield $key => $item;
sleep(2);
}
msg::info("fin gendata");
}
function _testRows(iterable $rows) {
$count = 0;
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);
}
}