diff --git a/src/Driver/Cnb/Day.php b/src/Driver/Cnb/Day.php index 3aa8056..c71f463 100644 --- a/src/Driver/Cnb/Day.php +++ b/src/Driver/Cnb/Day.php @@ -21,7 +21,7 @@ public function __construct( ClientInterface $client, RequestFactoryInterface $requestFactory, string $timeZone = 'Europe/Prague', - string $refresh = 'today 15:00:00', + string $refresh = 'today 14:45:00', ) { parent::__construct($client, $requestFactory, $timeZone, $refresh); diff --git a/src/Driver/RB/Day.php b/src/Driver/RB/Day.php index e4099bd..ef68a58 100644 --- a/src/Driver/RB/Day.php +++ b/src/Driver/RB/Day.php @@ -22,7 +22,7 @@ public function __construct( ClientInterface $client, RequestFactoryInterface $requestFactory, string $timeZone = 'Europe/Prague', - string $refresh = 'midnight, +1 minute', + string $refresh = 'midnight, +15 minute', ) { parent::__construct($client, $requestFactory, $timeZone, $refresh); diff --git a/src/RatingList/RatingList.php b/src/RatingList/RatingList.php index 4fd40ce..d67bc4e 100644 --- a/src/RatingList/RatingList.php +++ b/src/RatingList/RatingList.php @@ -2,18 +2,12 @@ namespace h4kuna\Exchange\RatingList; -use ArrayAccess; use DateTimeImmutable; use Generator; use h4kuna\Exchange\Currency\Property; use h4kuna\Exchange\Exceptions\FrozenMethodException; -use IteratorAggregate; -/** - * @implements IteratorAggregate - * @implements ArrayAccess - */ -final class RatingList implements RatingListInterface, IteratorAggregate, ArrayAccess +final class RatingList implements RatingListInterface { /** * @var array|null @@ -24,6 +18,8 @@ final class RatingList implements RatingListInterface, IteratorAggregate, ArrayA private ?DateTimeImmutable $date = null; + private ?DateTimeImmutable $expire = null; + public function __construct(private CacheEntity $cacheEntity, private RatingListCache $ratingListCache) { @@ -51,6 +47,7 @@ public function get(string $code): Property public function all(): array { if ($this->all === null) { + $this->getDate(); // init cache $this->all = $this->ratingListCache->all($this->cacheEntity); } @@ -61,12 +58,22 @@ public function all(): array public function getDate(): DateTimeImmutable { if ($this->date === null) { - $this->date = $this->ratingListCache->build($this->cacheEntity); + [ + 'date' => $this->date, + 'expire' => $this->expire, + ] = $this->ratingListCache->build($this->cacheEntity); } return $this->date; } + public function getExpire(): ?DateTimeImmutable + { + $this->getDate(); // init cache + return $this->expire; + } + + /** * @return Generator * @deprecated moved to class Exchange diff --git a/src/RatingList/RatingListCache.php b/src/RatingList/RatingListCache.php index 1ec66a5..a994895 100644 --- a/src/RatingList/RatingListCache.php +++ b/src/RatingList/RatingListCache.php @@ -6,6 +6,7 @@ use h4kuna\CriticalCache\CacheLocking; use h4kuna\CriticalCache\Utils\Dependency; use h4kuna\Exchange\Currency\Property; +use h4kuna\Exchange\Driver\Driver; use h4kuna\Exchange\Driver\DriverAccessor; use h4kuna\Exchange\Exceptions\InvalidStateException; use h4kuna\Exchange\Exceptions\UnknownCurrencyException; @@ -14,9 +15,12 @@ use Psr\Http\Client\ClientExceptionInterface; use Psr\SimpleCache\CacheInterface; +/** + * @phpstan-type cacheType array{date: DateTimeImmutable, expire: ?DateTimeImmutable, ttl: ?int} + */ final class RatingListCache { - public int $floatTtl = 600; // seconds -> 10 minutes + public int $floatTtl = 900; // seconds -> 15 minutes /** @@ -31,25 +35,34 @@ public function __construct( } - public function build(CacheEntity $cacheEntity): DateTimeImmutable + /** + * @return cacheType + */ + public function build(CacheEntity $cacheEntity): array { return $this->cache->load($cacheEntity->cacheKeyTtl, function ( Dependency $dependency, CacheInterface $cache, string $prefix, - ) use ($cacheEntity): DateTimeImmutable { - [$date, $ttl] = $this->buildCache($cacheEntity, $cache, $prefix); - $dependency->ttl = $ttl; + ) use ($cacheEntity): array { + $cacheType = $this->buildCache($cacheEntity, $cache, $prefix); + $dependency->ttl = $cacheType['ttl']; - return $date; + return $cacheType; }); } - public function rebuild(CacheEntity $cacheEntity): void + public function rebuild(CacheEntity $cacheEntity): bool { - [$date, $ttl] = $this->buildCache($cacheEntity, $this->cache, ''); - $this->cache->set($cacheEntity->cacheKeyTtl, $date, $ttl); + /** + * @var ?cacheType $cacheTypeOld + */ + $cacheTypeOld = $this->cache->get($cacheEntity->cacheKeyTtl); + $cacheType = $this->buildCache($cacheEntity, $this->cache, ''); + $this->cache->set($cacheEntity->cacheKeyTtl, $cacheType, $cacheType['ttl']); + + return $cacheTypeOld === null || $cacheTypeOld['expire']?->format(DATE_ATOM) !== $cacheType['expire']?->format(DATE_ATOM); } @@ -78,7 +91,7 @@ public function all(CacheEntity $cacheEntity): array /** - * @return array{DateTimeImmutable, ?int} + * @return array{date: DateTimeImmutable, expire: ?DateTimeImmutable, ttl: ?int} */ private function buildCache(CacheEntity $cacheEntity, CacheInterface $cache, string $prefix): array { @@ -93,7 +106,11 @@ private function buildCache(CacheEntity $cacheEntity, CacheInterface $cache, str $data = $cache->get($prefix . $cacheEntity->cacheKeyAll) ?? []; if ($cacheEntity->date === null && $data !== []) { - return [new DateTimeImmutable(), $this->floatTtl]; + return self::makeCacheData( + new DateTimeImmutable(), + new DateTimeImmutable(sprintf('+%s seconds', $this->floatTtl)), + $this->floatTtl, + ); } throw $e; } @@ -101,8 +118,36 @@ private function buildCache(CacheEntity $cacheEntity, CacheInterface $cache, str $cache->set($prefix . $cacheEntity->cacheKeyAll, $all); return $cacheEntity->date === null - ? [$provider->getDate(), Utils::countTTL($provider->getRefresh())] - : [$provider->getDate(), null]; + ? self::countCacheData($provider, $this->floatTtl) + : self::makeCacheData($provider->getDate()); + } + + + /** + * @return cacheType + */ + private static function makeCacheData( + DateTimeImmutable $date, + ?DateTimeImmutable $expire = null, + ?int $ttl = null + ): array + { + return [ + 'date' => $date, + 'expire' => $expire, + 'ttl' => $ttl, + ]; } + + /** + * @return cacheType + */ + private static function countCacheData(Driver $provider, int $floatTtl): array + { + $expire = $provider->getRefresh(); + $ttl = Utils::countTTL($expire, $floatTtl); + + return self::makeCacheData($provider->getDate(), DateTimeImmutable::createFromMutable($expire), $ttl); + } } diff --git a/src/RatingList/RatingListInterface.php b/src/RatingList/RatingListInterface.php index 29363ad..15e3c89 100644 --- a/src/RatingList/RatingListInterface.php +++ b/src/RatingList/RatingListInterface.php @@ -2,10 +2,16 @@ namespace h4kuna\Exchange\RatingList; +use ArrayAccess; use DateTimeImmutable; use h4kuna\Exchange\Currency\Property; +use IteratorAggregate; -interface RatingListInterface +/** + * @extends IteratorAggregate + * @extends ArrayAccess + */ +interface RatingListInterface extends IteratorAggregate, ArrayAccess { /** * @return self - clone or new object @@ -24,4 +30,7 @@ function all(): array; function getDate(): DateTimeImmutable; + + function getExpire(): ?DateTimeImmutable; + } diff --git a/src/Utils.php b/src/Utils.php index 388cd03..b575197 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -35,14 +35,14 @@ public static function transformCurrencies(array $currencies): array /** * @param int $beforeExpiration // 900 seconds -> 15 minutes */ - public static function countTTL(DateTime $dateTime, int $beforeExpiration = 900): int + public static function countTTL(DateTime $dateTime, int $beforeExpiration = 900, int $time = null): int { - $time = time(); + $time ??= time(); if (($dateTime->getTimestamp() - $beforeExpiration) <= $time) { $dateTime->modify('+1 day'); } - return $dateTime->getTimestamp() - time(); + return $dateTime->getTimestamp() - $time; } } diff --git a/tests/src/Caching/CacheTest.php b/tests/src/Caching/CacheTest.php index 1274b9f..ff4c6e3 100644 --- a/tests/src/Caching/CacheTest.php +++ b/tests/src/Caching/CacheTest.php @@ -21,7 +21,11 @@ public function testBasic(): void $cacheEntity = new Exchange\RatingList\CacheEntity(null, Exchange\Driver\Cnb\Day::class); $date = $cache->build($cacheEntity); - Assert::same('2022-12-21', $date->format('Y-m-d')); + Assert::same('2022-12-21', $date['date']->format('Y-m-d')); + $expected = new \DateTime('today 14:45:00'); + Exchange\Utils::countTTL($expected); + assert(isset($date['expire'])); + Assert::same($expected->format('Y-m-d H:i:s'), $date['expire']->format('Y-m-d H:i:s')); $cache->rebuild($cacheEntity); @@ -36,7 +40,8 @@ public function testHistory(): void $cacheEntity = new Exchange\RatingList\CacheEntity(new \DateTime('2022-12-01'), Exchange\Driver\Cnb\Day::class); $date = $cache->build($cacheEntity); - Assert::same('2022-12-01', $date->format('Y-m-d')); + Assert::same('2022-12-01', $date['date']->format('Y-m-d')); + Assert::null($date['expire']); $cache->rebuild($cacheEntity); diff --git a/tests/src/UtilsTest.php b/tests/src/UtilsTest.php index 71d1fe5..1304915 100644 --- a/tests/src/UtilsTest.php +++ b/tests/src/UtilsTest.php @@ -36,6 +36,24 @@ function (self $self) { ); }, ], + [ + function (self $self) { + $self->assert( + 87300, + new DateTime('2023-01-01 14:45:00'), + (new DateTime('2023-01-01 14:45:00, -900 seconds'))->getTimestamp(), + ); + }, + ], + [ + function (self $self) { + $self->assert( + 901, + new DateTime('2023-01-01 14:45:00'), + (new DateTime('2023-01-01 14:45:00, -901 seconds'))->getTimestamp(), + ); + }, + ], ]; } @@ -52,10 +70,15 @@ public function testCountTTL(Closure $assert): void public function assert( int $expectedTime, - DateTime $from + DateTime $from, + int $time = 0 ): void { - Assert::same($expectedTime, Utils::countTTL($from)); + if ($time === 0) { + Assert::same($expectedTime, Utils::countTTL($from, 900)); + } else { + Assert::same($expectedTime, Utils::countTTL($from, 900, $time)); + } } }