<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
(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
résolution du contenu
* 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 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); }
}

View File

@ -1,116 +1,128 @@
<?php
namespace nulib\cache;
use IteratorAggregate;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\php\time\DateTime;
use nulib\php\time\Delay;
use nulib\db\CapacitorStorage;
use nulib\ext\utils;
use nulib\php\func;
use Traversable;
class CacheChannel extends CapacitorChannel {
/** @var int durée de vie par défaut du cache */
const DURATION = "1D"; // jusqu'au lendemain
class CacheChannel extends CapacitorChannel implements IteratorAggregate {
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;
}
const INCLUDES = null;
const EXCLUDES = null;
const NAME = "cache";
const TABLE_NAME = "cache";
const COLUMN_DEFINITIONS = [
"group_id" => "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";
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];
const ADD_COLUMNS = null;
protected function COLUMN_DEFINITIONS(): ?array {
return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS);
}
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();
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 {
return cl::merge(self::get_cache_ids($item), [
"item" => null,
function getBaseFilter(): ?array {
return [
"group_id_" => $this->groupId,
"id_" => $this->id,
];
}
protected int $index = 0;
protected function getSearch($item): ?string {
$search = cl::filter_n(cl::with($item));
$search = implode(" ", $search);
return substr($search, 0, 255);
}
function getItemValues($item, $key=null): ?array {
$index = $this->index++;
$key = $key ?? $index;
$key = substr(strval($key), 0, 128);
$addColumns = static::ADD_COLUMNS ?? [];
$addColumns = cl::select($item,
array_filter(array_keys($addColumns), function ($key) {
return is_string($key);
}));
return cl::merge($addColumns, [
"group_id_" => $this->groupId,
"id_" => $this->id,
"key_index_" => $index,
"key_" => $key,
"search_" => $this->getSearch($item),
]);
}
function 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 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]);
}
return $count;
}
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 build(?iterable $items): self {
$this->delete(null);
$this->chargeAll($items);
return $this;
}
$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 {
$cacheIds = self::get_cache_ids($id);
$this->charge($cacheIds, null, [$duration]);
}
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;
}
}
}

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 nulib\cv;
use nulib\ext\utils;
use nulib\file;
use nulib\file\SharedFile;
use nulib\os\path;
@ -18,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();
@ -25,11 +31,8 @@ class CacheFile extends SharedFile {
throw ValueException::invalid_type($source, CacheData::class);
}
function __construct($file, ?array $params=null) {
if ($file === null) {
$rand = bin2hex(random_bytes(8));
$file = path::join(sys_get_temp_dir(), $rand);
}
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);
@ -38,7 +41,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;
@ -112,6 +115,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 */
@ -121,6 +126,7 @@ class CacheFile extends SharedFile {
[
"start" => $start,
"duration" => $duration,
"data" => $data,
"datafiles" => $datafilenames,
] = $this->unserialize(null, false, true);
if ($this->overrideDuration) {
@ -130,10 +136,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;
}
@ -163,15 +171,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);
}
@ -182,10 +191,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 */
@ -197,26 +206,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();
@ -232,8 +243,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);
}
@ -243,15 +259,16 @@ class CacheFile extends SharedFile {
return null;
}
function get($data=null, bool $noCache=false) {
return $this->action(function () use ($data, $noCache) {
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) {
@ -259,35 +276,78 @@ class CacheFile extends SharedFile {
$this->unlinkFiles(true);
$this->start = null;
$this->duration = null;
$this->data = null;
$updateData = true;
}
if ($defaultSource) {
if ($updateData) {
# calculer un fichier
# calculer la valeur
try {
$data = $source->get();
} 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);
throw $e;
}
} elseif (file_exists($datafile)) {
$data = $this->loadData($datafile);
$data = $this->loadDatafile($datafile);
} else {
$data = null;
}
if ($this->shouldCache($data)) {
$this->saveData($datafile, $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;
}
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;
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\ext\utils;
use nulib\php\func;
class cache {
protected static ?string $dbfile = null;
@ -17,30 +16,67 @@ class cache {
protected static ?CapacitorStorage $storage = null;
static function storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
}
static function set_storage(CapacitorStorage $storage): CapacitorStorage {
return self::$storage = $storage;
}
protected static function get_storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
protected static ?CacheManager $manager = null;
static function manager(): CacheManager {
return self::$manager ??= new CacheManager();
}
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 set_manager(CacheManager $manager): CacheManager {
return self::$manager = $manager;
}
static function get(): CacheChannel {
if (self::$channel !== null) return self::$channel;
else return self::set(null);
static function nc(bool $noCache=true, bool $reset=false): void {
self::manager()->setNoCache($noCache, $reset);
}
static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel {
$channel ??= new CacheDataChannel($id, $builder);
new Capacitor(self::get_storage(), $channel);
return $channel;
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;
});
$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;
$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);

View File

@ -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());

View File

@ -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.

View File

@ -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", [
"duration" => $duration,
"data" => new CacheData(function () {
$two = new CacheFile("two", new CacheData(function () {
return 2;
}),
}), [
"duration" => $duration,
]);
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=""
],
]);
Txx("three.0=", $three->get("data31"));
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);
}
}