modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2025-07-01 17:53:52 +04:00
parent df13496a86
commit 79b8c33d4f
9 changed files with 192 additions and 162 deletions

View File

@ -1,116 +1,127 @@
<?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
const INCLUDES = null;
const EXCLUDES = null;
class CacheChannel extends CapacitorChannel implements IteratorAggregate {
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";
static function with(CapacitorStorage $storage, ?iterable $rows=null, $cursorId=null): self {
$channel = (new static($cursorId))->initStorage($storage);
if ($rows !== null) $channel->build($rows);
return $channel;
}
static function verifix_id(&$cursorId): void {
$cursorId ??= utils::uuidgen();
if (is_array($cursorId)) {
$keys = array_keys($cursorId);
if (array_key_exists("group_id", $cursorId)) $groupIdKey = "group_id";
else $groupIdKey = $keys[1] ?? null;
$groupId = $id[$groupIdKey] ?? "";
if (array_key_exists("id", $id)) $idKey = "id";
$groupId = strval($cursorId[$groupIdKey] ?? "");
if (array_key_exists("id", $cursorId)) $idKey = "id";
else $idKey = $keys[0] ?? null;
$id = $id[$idKey] ?? "";
$id = strval($cursorId[$idKey] ?? "");
} else {
$groupId = "";
$id = strval($cursorId);
}
if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) {
# si le groupe est une classe, faire un hash du package pour limiter la
# longueur du groupe
[$package, $groupId] = [$ms[1], $ms[2]];
$package = substr(md5($package), 0, 4);
$groupId = "${groupId}_$package";
# si le groupe ou le nom sont trop grand, en faire un hash
if (strlen($groupId) > 32) {
$groupId = md5($groupId);
}
return ["group_id" => $groupId, "id" => $id];
if (strlen($id) > 128) {
$id = substr($id, 0, 128 - 32).md5($id);
}
$cursorId = ["group_id" => $groupId, "id" => $id];
}
function __construct(?string $duration=null, ?string $name=null) {
parent::__construct($name);
$this->duration = $duration ?? static::DURATION;
$this->includes = static::INCLUDES;
$this->excludes = static::EXCLUDES;
/**
* @param array|string $cursorId
*/
function __construct($cursorId) {
parent::__construct();
self::verifix_id($cursorId);
$this->cursorId = $cursorId;
}
protected string $duration;
protected array $cursorId;
protected ?array $includes;
function getCursorId(): array {
return $this->cursorId;
}
protected ?array $excludes;
function getBaseFilter(): ?array {
return $this->cursorId;
}
function getItemValues($item): ?array {
return cl::merge(self::get_cache_ids($item), [
"item" => null,
protected int $index = 0;
protected function getSearch($item): ?string {
$search = cl::filter_n(cl::with($item));
$search = implode(" ", $search);
return substr($search, 0, 255);
}
function getItemValues($item, $key=null): ?array {
$index = $this->index++;
$key = $key ?? $index;
$key = substr(strval($key), 0, 128);
return cl::merge($this->cursorId, [
"key_index" => $index,
"key" => $key,
"search" => $this->getSearch($item),
]);
}
function onCreate($item, array $row, ?array $alwaysNull, ?string $duration=null): ?array {
$now = new DateTime();
$duration ??= $this->duration;
return [
"date_start" => $now,
"duration" => new Delay($duration, $now),
];
function reset(bool $recreate=false): void {
$this->index = 0;
parent::reset($recreate);
}
function onUpdate($item, array $row, array $prow, ?string $duration=null): ?array {
$now = new DateTime();
$duration ??= $this->duration;
return [
"date_start" => $now,
"duration" => new Delay($duration, $now),
];
}
function shouldUpdate($id, bool $noCache=false): bool {
if ($noCache) return true;
$cacheIds = self::get_cache_ids($id);
$groupId = $cacheIds["group_id"];
if ($groupId) {
$includes = $this->includes;
$shouldInclude = $includes !== null && in_array($groupId, $includes);
$excludes = $this->excludes;
$shouldExclude = $excludes !== null && in_array($groupId, $excludes);
if (!$shouldInclude || $shouldExclude) return true;
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
$this->index = 0;
if ($items === null) return 0;
$count = 0;
if ($func !== null) $func = func::with($func, $args)->bind($this);
foreach ($items as $key => $item) {
$count += $this->charge($item, $func, [$key]);
}
$found = false;
$expired = false;
$this->each($cacheIds,
function($row) use (&$found, &$expired) {
$found = true;
$expired = $row["duration"]->isElapsed();
});
return !$found || $expired;
return $count;
}
function setCached($id, ?string $duration=null): void {
$cacheIds = self::get_cache_ids($id);
$this->charge($cacheIds, null, [$duration]);
function build(?iterable $items): self {
$this->reset(true);
$this->chargeAll($items);
return $this;
}
function resetCached($id) {
$cacheIds = self::get_cache_ids($id);
$this->delete($cacheIds);
function getIterator(): Traversable {
$rows = $this->dbAll([
"cols" => ["key", "item__"],
"where" => $this->getBaseFilter(),
]);
foreach ($rows as $row) {
$key = $row["key"];
$item = $this->unserialize($row["item__"]);
yield $key => $item;
}
}
}

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;
@ -26,10 +27,7 @@ class CacheFile extends SharedFile {
}
function __construct($file, $data=null, ?array $params=null) {
if ($file === null) {
$rand = bin2hex(random_bytes(8));
$file = path::join(sys_get_temp_dir(), $rand);
}
$file ??= path::join(sys_get_temp_dir(), utils::uuidgen());
$file = path::ensure_ext($file, self::EXT);
$this->basedir = path::dirname($file);
$basename = path::filename($file);

8
src/cache/TODO.md vendored Normal file
View File

@ -0,0 +1,8 @@
# nulib\cache
* CacheChannel
* spécifier des clés supplémentaires utilisable dans la recherche
* CacheFile
* pour $data===null, stocker dans le fichier de cache directement
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

37
src/cache/cache.php vendored
View File

@ -2,11 +2,9 @@
namespace nulib\cache;
use nulib\app;
use nulib\cache\CacheChannel;
use nulib\cache\CacheDataChannel;
use nulib\db\Capacitor;
use nulib\db\CapacitorStorage;
use nulib\db\sqlite\SqliteStorage;
use nulib\php\func;
class cache {
protected static ?string $dbfile = null;
@ -17,30 +15,21 @@ class cache {
protected static ?CapacitorStorage $storage = null;
protected static function storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
}
static function set_storage(CapacitorStorage $storage): CapacitorStorage {
return self::$storage = $storage;
}
protected static function get_storage(): CapacitorStorage {
return self::$storage ??= new SqliteStorage(self::dbfile());
}
protected static ?CacheChannel $channel = null;
static function set(?CacheChannel $channel): CacheChannel {
$channel ??= new CacheChannel();
new Capacitor(self::get_storage(), $channel);
return self::$channel = $channel;
}
static function get(): CacheChannel {
if (self::$channel !== null) return self::$channel;
else return self::set(null);
}
static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel {
$channel ??= new CacheDataChannel($id, $builder);
new Capacitor(self::get_storage(), $channel);
return $channel;
static function all(?iterable $rows, $cursorId=null): iterable {
CacheChannel::verifix_id($cursorId);
$file = "row-{$cursorId["group_id"]}-{$cursorId["id"]}";
$cache = new CacheFile($file, function() use ($rows, $cursorId) {
CacheChannel::with(self::storage(), null, $cursorId)->build($rows);
return $cursorId;
});
return CacheChannel::with(self::storage(), null, $cache->get());
}
}

2
tests/cache/.gitignore vendored Normal file
View File

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

22
tests/cache/CursorChannelTest.php vendored Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace nulib\cache;
use nulib\output\msg;
class CursorChannelTest extends _TestCase {
function testUsage() {
$data = [
"fr" => ["a" => "un", "b" => "deux"],
"eng" => ["a" => "one", "b" => "two"],
["a" => 1, "b" => 2],
];
$channel = CacheChannel::with(self::$storage, $data, "numbers");
$count = 0;
foreach ($channel as $key => $item) {
msg::info("one: $key => {$item["a"]}");
$count++;
}
self::assertSame(3, $count);
}
}

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__."/capacitor.db");
cache::set_storage(self::$storage);
}
static function tearDownAfterClass(): void {
parent::tearDownAfterClass();
self::$storage->close();
}
}

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

@ -0,0 +1,30 @@
<?php
namespace nulib\cache;
use nulib\output\msg;
class cacheTest extends _TestCase {
function gendata() {
$data = [
"fr" => ["a" => "un", "b" => "deux"],
"eng" => ["a" => "one", "b" => "two"],
["a" => 1, "b" => 2],
];
foreach ($data as $key => $item) {
msg::info("yield $key");
yield $key => $item;
sleep(2);
}
msg::info("fin gendata");
}
function testUsage() {
$data = cache::all($this->gendata(),"gendata");
$count = 0;
foreach ($data as $key => $item) {
msg::info("got $key => ".var_export($item, true));
$count++;
}
self::assertSame(3, $count);
}
}