From 4b1b5c147e751f50c0d36125c0727b4a143d14f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Mat=C4=9Bj=C4=8Dek?= Date: Wed, 18 Oct 2023 12:28:04 +0200 Subject: [PATCH] Invalid cache before expiration --- README.md | 16 ++++++++++++++++ composer.json | 2 ++ src/Driver/Cnb/Day.php | 4 +++- src/Driver/Driver.php | 19 +++++++++++-------- src/Driver/DriverBuilderFactory.php | 1 - src/Driver/Ecb/Day.php | 4 +++- src/RatingList/CacheEntity.php | 3 +++ src/RatingList/RatingListCache.php | 18 ++++-------------- src/Utils.php | 14 ++++++++++++++ tests/src/Driver/Cnb/DayTest.php | 21 +++++++++++++++++++++ tests/src/TimestampTimeZoneTest.php | 29 +++++++++++++++++++++++++++++ 11 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 tests/src/TimestampTimeZoneTest.php diff --git a/README.md b/README.md index 4b0ba05..a5c76cd 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,19 @@ foreach ($exchange as $code => $property) { var_dump($property); } ``` + +## Caching + +The cache invalid automatic at some time, defined by property `Driver::$refresh`. From this property is counted time to live. Little better is invalid cache by cron. Because one request on server does not lock other requests. Let's run cron max. 15 minutes before invalidate cache. +```php +use h4kuna\Exchange\RatingList\RatingListCache; +use h4kuna\Exchange\RatingList\CacheEntity; +use h4kuna\Exchange\Driver\Cnb\Day; + +/** @var RatingListCache $ratingListCache */ +$ratingListCache->rebuild(new CacheEntity(null, Day::class)); +``` + +In example, is used `h4kuna\Exchange\Driver\Cnb\Day::$refresh` is defined at 15:00. Run cron 14:55 every day. + + diff --git a/composer.json b/composer.json index f2d34d0..85f20fb 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,8 @@ "suggest": { "guzzlehttp/guzzle": "As default implementation for PSR standards.", "guzzlehttp/psr7": "Minimum ^2.4 for guzzle.", + "h4kuna/dir": "If you want to use build-in factory.", + "nette/caching": "If you have not own PSR-6 implementation.", "ext-simplexml": "If you want to use h4kuna\\Exchange\\Driver\\Ecb." }, "autoload-dev": { diff --git a/src/Driver/Cnb/Day.php b/src/Driver/Cnb/Day.php index 022e787..9472e8e 100644 --- a/src/Driver/Cnb/Day.php +++ b/src/Driver/Cnb/Day.php @@ -12,6 +12,8 @@ class Day extends Exchange\Driver\Driver { // private const URL_DAY_OTHER = 'http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_ostatnich_men/kurzy.txt'; + public static string $url = 'https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt'; + protected string $refresh = 'today 15:00:00'; protected string $timeZone = 'Europe/Prague'; @@ -47,7 +49,7 @@ protected function createProperty($row): Property protected function prepareUrl(?\DateTimeInterface $date): string { - $url = 'https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt'; + $url = self::$url; if ($date === null) { return $url; diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index 7b5e5c3..f8cd7ca 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -3,6 +3,8 @@ namespace h4kuna\Exchange\Driver; use DateTime; +use DateTimeImmutable; +use DateTimeInterface; use DateTimeZone; use Generator; use h4kuna\Exchange; @@ -20,7 +22,7 @@ abstract class Driver { - private \DateTimeImmutable $date; + private DateTimeImmutable $date; protected string $timeZone = 'UTC'; @@ -43,14 +45,14 @@ public function __construct( /** * @throws ClientExceptionInterface */ - public function initRequest(?\DateTimeInterface $date): void + public function initRequest(?DateTimeInterface $date): void { $content = $this->client->sendRequest($this->createRequest($date)); $this->list = $this->createList($content); } - public function getDate(): \DateTimeImmutable + public function getDate(): DateTimeImmutable { return $this->date; } @@ -84,7 +86,7 @@ public function getRefresh(): DateTime protected function setDate(string $format, string $value): void { - $date = \DateTimeImmutable::createFromFormat($format, $value, new DateTimeZone($this->timeZone)); + $date = DateTimeImmutable::createFromFormat($format, $value, new DateTimeZone($this->timeZone)); if ($date === false) { throw new Exchange\Exceptions\InvalidStateException(sprintf('Can not create DateTime object from source "%s" with format "%s".', $value, $format)); } @@ -105,14 +107,15 @@ abstract protected function createList(ResponseInterface $response): iterable; abstract protected function createProperty($row); - abstract protected function prepareUrl(?\DateTimeInterface $date): string; + abstract protected function prepareUrl(?DateTimeInterface $date): string; - private function createRequest(?\DateTimeInterface $date): RequestInterface + private function createRequest(?DateTimeInterface $date): RequestInterface { if ($date !== null && $date->getTimezone()->getName() !== $this->timeZone) { - $date = new DateTime('@' . $date->getTimestamp(), new DateTimeZone('UTC')); - $date->setTimezone(new DateTimeZone($this->timeZone)); + $tmp = new DateTime('now', new DateTimeZone($this->timeZone)); + $tmp->setTimestamp($date->getTimestamp()); + $date = $tmp; } $request = $this->requestFactory->createRequest('GET', $this->prepareUrl($date)); diff --git a/src/Driver/DriverBuilderFactory.php b/src/Driver/DriverBuilderFactory.php index affca76..5d32e9c 100644 --- a/src/Driver/DriverBuilderFactory.php +++ b/src/Driver/DriverBuilderFactory.php @@ -54,7 +54,6 @@ protected function getRequestFactory(): RequestFactoryInterface { if ($this->requestFactory === null) { MissingDependencyException::guzzleFactory(); - $this->requestFactory = new HttpFactory(); } diff --git a/src/Driver/Ecb/Day.php b/src/Driver/Ecb/Day.php index cd5f7f8..2a5f2fb 100644 --- a/src/Driver/Ecb/Day.php +++ b/src/Driver/Ecb/Day.php @@ -12,6 +12,8 @@ class Day extends Exchange\Driver\Driver { protected string $timeZone = 'Europe/Berlin'; + public static string $url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'; + /** * @return iterable<\SimpleXMLElement> @@ -55,7 +57,7 @@ protected function prepareUrl(?\DateTimeInterface $date): string throw new Exchange\Exceptions\InvalidStateException('Ecb does not support history.'); } - return 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'; + return self::$url; } } diff --git a/src/RatingList/CacheEntity.php b/src/RatingList/CacheEntity.php index d9186f8..43b9b21 100644 --- a/src/RatingList/CacheEntity.php +++ b/src/RatingList/CacheEntity.php @@ -4,6 +4,9 @@ use DateTimeInterface; +/** + * readonly php 8.2+ + */ final class CacheEntity { public ?DateTimeInterface $date; diff --git a/src/RatingList/RatingListCache.php b/src/RatingList/RatingListCache.php index 58f3cae..6a95d44 100644 --- a/src/RatingList/RatingListCache.php +++ b/src/RatingList/RatingListCache.php @@ -2,7 +2,6 @@ namespace h4kuna\Exchange\RatingList; -use DateTime; use DateTimeImmutable; use h4kuna\CriticalCache\CacheLocking; use h4kuna\CriticalCache\Utils\Dependency; @@ -10,13 +9,14 @@ use h4kuna\Exchange\Driver\DriverAccessor; use h4kuna\Exchange\Exceptions\InvalidStateException; use h4kuna\Exchange\Exceptions\UnknownCurrencyException; +use h4kuna\Exchange\Utils; use h4kuna\Serialize\Serialize; use Psr\Http\Client\ClientExceptionInterface; use Psr\SimpleCache\CacheInterface; final class RatingListCache { - public int $floatTtl = 600; + public int $floatTtl = 600; // seconds -> 10 minutes /** @@ -89,7 +89,7 @@ private function buildCache(CacheEntity $cacheEntity, CacheInterface $cache, str $data = $cache->get($prefix . $cacheEntity->cacheKeyAll) ?? []; if ($cacheEntity->date === null && $data !== []) { - return [new DateTimeImmutable(), time() + $this->floatTtl]; + return [new DateTimeImmutable(), $this->floatTtl]; } throw $e; } @@ -103,18 +103,8 @@ private function buildCache(CacheEntity $cacheEntity, CacheInterface $cache, str $cache->set($prefix . $cacheEntity->cacheKeyAll, $all); return $cacheEntity->date === null - ? [$provider->getDate(), self::countTTL($provider->getRefresh())] + ? [$provider->getDate(), Utils::countTTL($provider->getRefresh())] : [$provider->getDate(), null]; } - - private static function countTTL(DateTime $dateTime): int - { - if ($dateTime->getTimestamp() < time()) { - $dateTime->modify('+1 day'); - } - - return $dateTime->getTimestamp(); - } - } diff --git a/src/Utils.php b/src/Utils.php index e02bf92..8abbcc7 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -2,6 +2,7 @@ namespace h4kuna\Exchange; +use DateTime; use h4kuna\DataType\Basic\Strings; use Nette\StaticClass; @@ -30,4 +31,17 @@ public static function transformCurrencies(array $currencies): array return array_flip(array_map(fn (string $v) => strtoupper($v), $currencies)); } + + /** + * @param int $beforeExpiration // 900 seconds -> 15 minutes + */ + public static function countTTL(DateTime $dateTime, int $beforeExpiration = 900): int + { + if (($dateTime->getTimestamp() - $beforeExpiration) < time()) { + $dateTime->modify('+1 day'); + } + + return $dateTime->getTimestamp() - time(); + } + } diff --git a/tests/src/Driver/Cnb/DayTest.php b/tests/src/Driver/Cnb/DayTest.php index ce21fda..a8b0031 100644 --- a/tests/src/Driver/Cnb/DayTest.php +++ b/tests/src/Driver/Cnb/DayTest.php @@ -2,6 +2,10 @@ namespace h4kuna\Exchange\Tests\Driver\Cnb; +use GuzzleHttp\Client; +use GuzzleHttp\Psr7\HttpFactory; +use h4kuna\Exchange\Driver\Cnb\Day; +use h4kuna\Exchange\Utils; use Tester\Assert; use Tester\TestCase; @@ -20,6 +24,23 @@ public function testDownloadHistory(): void Assert::same('2022-12-01', $exchange->getDate()->format('Y-m-d')); } + + public function testRefresh(): void + { + $client = new HttpFactory(); + $day = new Day(new Client(), $client); + + Assert::same((new \DateTime('now', new \DateTimeZone('Europe/Prague')))->format('Y-m-d'), $day->getRefresh()->format('Y-m-d')); + + $prevTtl = 900; + $refresh = new \DateTime('today 15:00:00', new \DateTimeZone('Europe/Prague')); + if ($refresh->getTimestamp() < (time() - $prevTtl)) { + $refresh->modify('+1 day'); + } + + Assert::same($refresh->getTimestamp() - time(), Utils::countTTL($day->getRefresh(), $prevTtl)); + } + } (new DayTest())->run(); diff --git a/tests/src/TimestampTimeZoneTest.php b/tests/src/TimestampTimeZoneTest.php new file mode 100644 index 0000000..9aa9589 --- /dev/null +++ b/tests/src/TimestampTimeZoneTest.php @@ -0,0 +1,29 @@ +setTimestamp($date->getTimestamp()); + + Assert::same('1986-12-30 05:30:57', $date->format('Y-m-d H:i:s')); + Assert::same('1986-12-30 16:30:57', $newDate->format('Y-m-d H:i:s')); + } +} + +(new TimestampTimeZoneTest)->run();