src/Internal/Service/StateService.php
<?php
declare(strict_types=1);
namespace Bavix\Wallet\Internal\Service;
use Illuminate\Contracts\Cache\Factory as CacheFactory;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
final readonly class StateService implements StateServiceInterface
{
private const RANDOM_BYTES = 3;
/**
* Keeps the state of balance
*/
private const PREFIX_STATE = 'wallet_s::';
/**
* Stores a callback reference
*/
private const PREFIX_FORK_REF = 'wallet_f::';
/**
* Stores a pair of uuid with forkId
*/
private const PREFIX_FORK_ID = 'wallet_fc::';
/**
* Stores all uuids for a particular forkId
*/
private const PREFIX_HASHMAP = 'wallet_hm::';
private CacheRepository $store;
public function __construct(CacheFactory $cacheFactory)
{
$this->store = $cacheFactory->store('array');
}
/**
* @param string[] $uuids
* @param callable(): array<string, string> $value
*/
public function multiFork(array $uuids, callable $value): void
{
$forkId = $this->getForkId();
$values = [
self::PREFIX_FORK_REF.$forkId => $value,
self::PREFIX_HASHMAP.$forkId => $uuids,
];
foreach ($uuids as $uuid) {
$values[self::PREFIX_STATE.$uuid] = null;
$values[self::PREFIX_FORK_ID.$uuid] = $forkId;
}
$this->store->setMultiple($values);
}
public function get(string $uuid): ?string
{
$value = $this->store->get(self::PREFIX_STATE.$uuid);
if ($value !== null) {
return $value;
}
$forkId = $this->store->pull(self::PREFIX_FORK_ID.$uuid);
if ($forkId === null) {
return null;
}
/** @var null|callable(): array<string, string> $callable */
$callable = $this->store->pull(self::PREFIX_FORK_REF.$forkId);
if ($callable === null) {
return null;
}
$insertValues = [];
$results = $callable();
foreach ($results as $rUuid => $rValue) {
$insertValues[self::PREFIX_STATE.$rUuid] = $rValue;
}
// set new values
$this->store->setMultiple($insertValues);
/** @var array<string> $uuids */
$uuids = $this->store->pull(self::PREFIX_HASHMAP.$forkId, []);
$deleteKeys = array_map(static fn (string $uuid) => self::PREFIX_FORK_ID.$uuid, $uuids);
// delete callables by uuids
$this->store->deleteMultiple($deleteKeys);
return $results[$uuid] ?? null;
}
public function drop(string $uuid): void
{
$deleteKeys = [self::PREFIX_STATE.$uuid];
$forkId = $this->store->pull(self::PREFIX_FORK_ID.$uuid);
if ($forkId !== null) {
$deleteKeys[] = self::PREFIX_FORK_REF.$forkId;
$deleteKeys[] = self::PREFIX_HASHMAP.$forkId;
}
$this->store->deleteMultiple($deleteKeys);
}
private function getForkId(): string
{
do {
$forkId = bin2hex(random_bytes(self::RANDOM_BYTES));
} while ($this->store->has(self::PREFIX_FORK_REF.$forkId));
return $forkId;
}
}