Compare commits

..

No commits in common. "019f4333c05808d527c8757e01a82816fa28678f" and "2d09a584caa0a88fa0ab96afc0fc9942b88a6749" have entirely different histories.

15 changed files with 215 additions and 530 deletions

47
TODO.md
View File

@ -73,53 +73,6 @@
~~~
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,9 +99,6 @@ 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,128 +1,116 @@
<?php
namespace nulib\cache;
use IteratorAggregate;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage;
use nulib\ext\utils;
use nulib\php\func;
use Traversable;
use nulib\php\time\DateTime;
use nulib\php\time\Delay;
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;
}
class CacheChannel extends CapacitorChannel {
/** @var int durée de vie par défaut du cache */
const DURATION = "1D"; // jusqu'au lendemain
const NAME = "cache";
const TABLE_NAME = "cache";
const INCLUDES = null;
const EXCLUDES = null;
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_)",
"group_id" => "varchar(64) not null",
"id" => "varchar(64) not null",
"date_start" => "datetime",
"duration_" => "text",
"primary key (group_id, id)",
];
const ADD_COLUMNS = null;
protected function COLUMN_DEFINITIONS(): ?array {
return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS);
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];
}
/**
* @param array|string $cursorId
*/
function __construct($cursorId) {
parent::__construct();
cache::verifix_id($cursorId);
[
"group_id" => $this->groupId,
"id" => $this->id,
] = $cursorId;
function __construct(?string $duration=null, ?string $name=null) {
parent::__construct($name);
$this->duration = $duration ?? static::DURATION;
$this->includes = static::INCLUDES;
$this->excludes = static::EXCLUDES;
}
protected string $groupId;
protected string $duration;
protected string $id;
protected ?array $includes;
function getCursorId(): array {
return [
"group_id" => $this->groupId,
"id" => $this->id,
];
}
protected ?array $excludes;
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 getItemValues($item): ?array {
return cl::merge(self::get_cache_ids($item), [
"item" => null,
]);
}
function reset(bool $recreate=false): void {
$this->index = 0;
parent::reset($recreate);
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 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]);
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;
}
return $count;
$found = false;
$expired = false;
$this->each($cacheIds,
function($row) use (&$found, &$expired) {
$found = true;
$expired = $row["duration"]->isElapsed();
});
return !$found || $expired;
}
function build(?iterable $items): self {
$this->delete(null);
$this->chargeAll($items);
return $this;
function setCached($id, ?string $duration=null): void {
$cacheIds = self::get_cache_ids($id);
$this->charge($cacheIds, null, [$duration]);
}
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;
}
function resetCached($id) {
$cacheIds = self::get_cache_ids($id);
$this->delete($cacheIds);
}
}

53
src/cache/CacheDataChannel.php vendored Normal file
View File

@ -0,0 +1,53 @@
<?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,7 +3,6 @@ namespace nulib\cache;
use Exception;
use nulib\cv;
use nulib\ext\utils;
use nulib\file;
use nulib\file\SharedFile;
use nulib\os\path;
@ -19,11 +18,6 @@ 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();
@ -31,8 +25,11 @@ class CacheFile extends SharedFile {
throw ValueException::invalid_type($source, CacheData::class);
}
function __construct(?string $file, $data=null, ?array $params=null) {
$file ??= path::join(sys_get_temp_dir(), utils::uuidgen());
function __construct($file, ?array $params=null) {
if ($file === null) {
$rand = bin2hex(random_bytes(8));
$file = path::join(sys_get_temp_dir(), $rand);
}
$file = path::ensure_ext($file, self::EXT);
$this->basedir = path::dirname($file);
$basename = path::filename($file);
@ -41,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;
@ -115,8 +112,6 @@ 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 */
@ -126,7 +121,6 @@ class CacheFile extends SharedFile {
[
"start" => $start,
"duration" => $duration,
"data" => $data,
"datafiles" => $datafilenames,
] = $this->unserialize(null, false, true);
if ($this->overrideDuration) {
@ -136,12 +130,10 @@ class CacheFile extends SharedFile {
} else {
$start = null;
$duration = null;
$data = null;
$datafilenames = [];
}
$this->start = $start;
$this->duration = $duration;
$this->data = $data;
$this->datafilenames = $datafilenames;
}
@ -171,16 +163,15 @@ class CacheFile extends SharedFile {
$this->serialize([
"start" => $this->start,
"duration" => $this->duration,
"data" => $this->data,
"datafiles" => $datafilenames,
], false, true);
}
protected function loadDatafile(string $datafile) {
protected function loadData(string $datafile) {
return file::reader($datafile)->unserialize();
}
protected function saveDatafile(string $datafile, $data): void {
protected function saveData(string $datafile, $data): void {
file::writer($datafile)->serialize($data);
}
@ -191,10 +182,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 */
@ -206,28 +197,26 @@ 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() {
$modified = false;
if ($this->start != $this->ostart) $modified = true;
$start = $this->start;
$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 $data et $datafiles
if ($this->data !== $this->odata) $modified = true;
if ($this->datafilenames !== $this->odatafilenames) $modified = true;
# égalité stricte uniquement pour $datafiles qui est un array
if ($datafilenames !== $this->odatafilenames) $modified = true;
if ($modified && !$this->readonly) {
$this->lockWrite();
$this->saveMetadata();
@ -243,13 +232,8 @@ 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);
}
@ -259,95 +243,51 @@ class CacheFile extends SharedFile {
return null;
}
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);
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);
$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 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;
$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 ($this->shouldCache($data)) $this->data = $data;
else $this->data = $data = null;
} else {
if ($updateData) {
# calculer la valeur
# calculer un fichier
try {
$data = $source->get();
} catch (Exception $e) {
# ne pas garder le fichier destination en cas d'exception
# ne pas garder le fichier en cas d'exception
$this->unlinkDatafile($datafile);
throw $e;
}
} elseif (file_exists($datafile)) {
$data = $this->loadDatafile($datafile);
$data = $this->loadData($datafile);
} else {
$data = null;
}
if ($this->shouldCache($data)) {
$this->saveDatafile($datafile, $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;
}
} 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);
return $data;
});
}

View File

@ -1,68 +0,0 @@
<?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,10 +2,11 @@
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;
@ -16,67 +17,30 @@ 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 ?CacheManager $manager = null;
static function manager(): CacheManager {
return self::$manager ??= new CacheManager();
protected static function get_storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
}
static function set_manager(CacheManager $manager): CacheManager {
return self::$manager = $manager;
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 nc(bool $noCache=true, bool $reset=false): void {
self::manager()->setNoCache($noCache, $reset);
static function get(): CacheChannel {
if (self::$channel !== null) return self::$channel;
else return self::set(null);
}
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));
static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel {
$channel ??= new CacheDataChannel($id, $builder);
new Capacitor(self::get_storage(), $channel);
return $channel;
}
}

View File

@ -98,7 +98,7 @@ abstract class AbstractStorageApp extends Application {
}
}
$first = true;
$capacitor->each($filter, function ($row) use (&$first) {
$capacitor->each($filter, function ($item, $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, null, [
$cache = new CacheFile($file, [
"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, null, [
$cache = new CacheFile($file, [
"readonly" => true,
]);
yaml::dump($cache->getInfos());

View File

@ -21,33 +21,6 @@
* `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, [
$null = new CacheFile("null", [
"duration" => $duration,
]);
show("null", $null);
}
if (in_array("one", $what)) {
$one = new class("one", null, [
$one = new class("one", [
"duration" => $duration,
]) extends CacheFile {
protected function compute() {
@ -43,10 +43,11 @@ if (in_array("one", $what)) {
}
if (in_array("two", $what)) {
$two = new CacheFile("two", new CacheData(function () {
return 2;
}), [
$two = new CacheFile("two", [
"duration" => $duration,
"data" => new CacheData(function () {
return 2;
}),
]);
show("two", $two);
}
@ -61,10 +62,12 @@ if (in_array("three", $what)) {
});
$three = new CacheFile("three", [
"data31" => $data31,
$data31, # name=data31name
"data32" => $data32,
$data32, # name=""
"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
View File

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

View File

@ -1,38 +0,0 @@
<?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);
}
}

View File

@ -1,23 +0,0 @@
<?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();
}
}

View File

@ -1,55 +0,0 @@
<?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);
}
}