diff --git a/TODO.md b/TODO.md index 1c71852..0637136 100644 --- a/TODO.md +++ b/TODO.md @@ -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 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); } } diff --git a/src/cache/CacheChannel.php b/src/cache/CacheChannel.php index 4272a99..68fde79 100644 --- a/src/cache/CacheChannel.php +++ b/src/cache/CacheChannel.php @@ -1,116 +1,128 @@ 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 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->delete(null); + $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 3feb150..a4684f7 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; @@ -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,51 +259,95 @@ 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) { + $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 (!$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; - } + $updateMetadata = $this->shouldUpdate($noCache); + if ($defaultSource) $updateData = $this->data === null; + else $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; + $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 (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); }); } 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 46f93b0..b0a25c2 100644 --- a/src/cache/cache.php +++ b/src/cache/cache.php @@ -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)); } } 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/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. 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")); 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/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/_TestCase.php b/tests/cache/_TestCase.php new file mode 100644 index 0000000..f05875c --- /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..9d24daa --- /dev/null +++ b/tests/cache/cacheTest.php @@ -0,0 +1,55 @@ + ["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); + } +}