-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a05b6fe
commit 848eb1a
Showing
5 changed files
with
273 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
namespace Phikl\Cache; | ||
|
||
use Psr\SimpleCache\CacheInterface; | ||
|
||
/** | ||
* Simple implementation of the PSR-16 CacheInterface using APCu | ||
* for the Pkl modules evaluation cache. | ||
*/ | ||
final class ApcuCache implements CacheInterface | ||
{ | ||
/** | ||
* @param non-empty-string $key | ||
*/ | ||
public function get(string $key, mixed $default = null): Entry|null | ||
{ | ||
if ($default !== null && !$default instanceof Entry) { | ||
throw new \InvalidArgumentException('Default value must be null or an instance of Entry'); | ||
} | ||
|
||
$entry = \apcu_fetch($key); | ||
if ($entry === false) { | ||
return $default; | ||
} | ||
|
||
$entry = @unserialize($entry); | ||
if ($entry === false) { | ||
return $default; | ||
} | ||
|
||
return $entry; | ||
} | ||
|
||
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool | ||
{ | ||
if (!$value instanceof Entry) { | ||
return false; | ||
} | ||
|
||
return \apcu_store( | ||
$key, | ||
serialize($value), | ||
$ttl instanceof \DateInterval ? (int) ($ttl->format('U')) - \time() : ($ttl ?? 0) | ||
); | ||
} | ||
|
||
public function delete(string $key): bool | ||
{ | ||
return \apcu_delete($key); | ||
} | ||
|
||
/** | ||
* Caution, this method will clear the entire cache, not just the cache for this application. | ||
*/ | ||
public function clear(): bool | ||
{ | ||
return \apcu_clear_cache(); | ||
} | ||
|
||
/** | ||
* @param iterable<non-empty-string> $keys | ||
* | ||
* @return array<non-empty-string, Entry|null> | ||
*/ | ||
public function getMultiple(iterable $keys, mixed $default = null): array | ||
{ | ||
$entries = []; | ||
foreach ($keys as $key) { | ||
$entries[$key] = $this->get($key, $default); | ||
} | ||
|
||
return $entries; | ||
} | ||
|
||
/** | ||
* @param iterable<string, Entry> $values | ||
*/ | ||
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool | ||
{ | ||
foreach ($values as $key => $value) { | ||
if (!$this->set($key, $value, $ttl)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @param iterable<string> $keys | ||
*/ | ||
public function deleteMultiple(iterable $keys): bool | ||
{ | ||
$success = true; | ||
foreach ($keys as $key) { | ||
if (!$this->delete($key)) { | ||
$success = false; | ||
} | ||
} | ||
|
||
return $success; | ||
} | ||
|
||
public function has(string $key): bool | ||
{ | ||
return \apcu_exists($key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
|
||
namespace Cache; | ||
|
||
use Phikl\Cache\ApcuCache; | ||
use Phikl\Cache\Entry; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
use PHPUnit\Framework\Attributes\RequiresPhpExtension; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
#[RequiresPhpExtension('apcu')] | ||
#[CoversClass(ApcuCache::class)] | ||
class ApcuCacheTest extends TestCase | ||
{ | ||
protected function tearDown(): void | ||
{ | ||
\apcu_clear_cache(); | ||
} | ||
|
||
public function testGetWithDefaultOtherThanEntryInstance(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$this->expectException(\InvalidArgumentException::class); | ||
$this->expectExceptionMessage('Default value must be null or an instance of Entry'); | ||
|
||
$cache->get('key', 'invalid'); | ||
} | ||
|
||
public function testGetReturnsDefaultIfKeyDoesNotExist(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry = new Entry('content', 'hash', 0); | ||
|
||
$this->assertNull($cache->get('nonexistent')); | ||
$this->assertSame($entry, $cache->get('nonexistent', $entry)); | ||
$this->assertFalse($cache->has('nonexistent')); | ||
} | ||
|
||
public function testGetOnValidSetEntry(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry = new Entry('content', 'hash', $time = \time()); | ||
$cache->set('key', $entry); | ||
|
||
$entry = $cache->get('key'); | ||
$this->assertInstanceOf(Entry::class, $entry); | ||
$this->assertSame('content', $entry->content); | ||
$this->assertSame('hash', $entry->hash); | ||
$this->assertSame($time, $entry->timestamp); | ||
} | ||
|
||
public function testSetReturnsFalseOnInvalidEntry(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$this->assertFalse($cache->set('key', 'invalid')); | ||
} | ||
|
||
public function testDeleteEntry(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry = new Entry('content', 'hash', 0); | ||
$cache->set('key', $entry); | ||
|
||
$this->assertTrue($cache->delete('key')); | ||
$this->assertNull($cache->get('key')); | ||
} | ||
|
||
public function testClear(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry = new Entry('content', 'hash', 0); | ||
$cache->set('key', $entry); | ||
|
||
$this->assertTrue($cache->clear()); | ||
$this->assertNull($cache->get('key')); | ||
} | ||
|
||
public function testGetSetMultiple(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry1 = new Entry('content1', 'hash1', 0); | ||
$entry2 = new Entry('content2', 'hash2', 0); | ||
$entry3 = new Entry('content3', 'hash3', 0); | ||
|
||
$cache->setMultiple([ | ||
'key1' => $entry1, | ||
'key2' => $entry2, | ||
'key3' => $entry3, | ||
]); | ||
|
||
$entries = $cache->getMultiple(['key1', 'key2', 'key3']); | ||
|
||
$this->assertArrayHasKey('key1', $entries); | ||
$this->assertArrayHasKey('key2', $entries); | ||
$this->assertArrayHasKey('key3', $entries); | ||
|
||
$this->assertInstanceOf(Entry::class, $entries['key1']); | ||
$this->assertSame('content1', $entries['key1']->content); | ||
$this->assertSame('hash1', $entries['key1']->hash); | ||
|
||
$this->assertInstanceOf(Entry::class, $entries['key2']); | ||
$this->assertSame('content2', $entries['key2']->content); | ||
$this->assertSame('hash2', $entries['key2']->hash); | ||
|
||
$this->assertInstanceOf(Entry::class, $entries['key3']); | ||
$this->assertSame('content3', $entries['key3']->content); | ||
$this->assertSame('hash3', $entries['key3']->hash); | ||
} | ||
|
||
public function testDeleteMultiple(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry1 = new Entry('content1', 'hash1', 0); | ||
$entry2 = new Entry('content2', 'hash2', 0); | ||
$entry3 = new Entry('content3', 'hash3', 0); | ||
|
||
$cache->setMultiple([ | ||
'key1' => $entry1, | ||
'key2' => $entry2, | ||
'key3' => $entry3, | ||
]); | ||
|
||
$this->assertTrue($cache->deleteMultiple(['key1', 'key2'])); | ||
$this->assertNull($cache->get('key1')); | ||
$this->assertNull($cache->get('key2')); | ||
$this->assertNotNull($cache->get('key3')); | ||
} | ||
|
||
public function testHas(): void | ||
{ | ||
$cache = new ApcuCache(); | ||
|
||
$entry = new Entry('content', 'hash', 0); | ||
$cache->set('key', $entry); | ||
|
||
$this->assertTrue($cache->has('key')); | ||
$this->assertFalse($cache->has('invalid')); | ||
} | ||
} |