Skip to content

Commit 7b23f89

Browse files
authored
Handle exceptions generating cache values (#70)
* feature: handle exceptions generating cache values closes #13 * feature: handle exceptions generating cache values closes #13 * ci: codacy
1 parent 20f2742 commit 7b23f89

6 files changed

Lines changed: 90 additions & 4 deletions

File tree

.codacy.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
exclude_paths:
2+
- ".github/**"
3+
- "example/**"
4+
- "test/**"
5+

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ $location = $fileCache->get("lat-lon", $lookup);
4444
echo "Your location is: $location";
4545
```
4646

47+
If generating a fresh value fails, throw `Gt\FileCache\CacheValueGenerationException`
48+
from the callback. The cache will ignore that failure and skip writing a replacement
49+
value, which leaves `null` available as a legitimate cached value.
50+
4751
# Proudly sponsored by
4852

4953
[JetBrains Open Source sponsorship program](https://www.jetbrains.com/community/opensource/)

example/01-latlon.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,26 @@
1717
function httpJson(string $uri):object {
1818
$ch = curl_init($uri);
1919
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
20-
return json_decode(curl_exec($ch));
20+
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
21+
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
22+
23+
$response = curl_exec($ch);
24+
if($response === false) {
25+
throw new Gt\FileCache\CacheValueGenerationException(curl_error($ch));
26+
}
27+
28+
try {
29+
return json_decode($response, flags: JSON_THROW_ON_ERROR);
30+
}
31+
catch(JsonException $exception) {
32+
throw new Gt\FileCache\CacheValueGenerationException(
33+
"Invalid JSON returned from $uri",
34+
previous: $exception,
35+
);
36+
}
37+
finally {
38+
curl_close($ch);
39+
}
2140
}
2241

2342
$ipAddress = $fileCache->get("ip", function():string {

src/Cache.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ public function get(
2929
return $this->fileAccess->getData($name);
3030
}
3131
catch(FileNotFoundException|CacheInvalidException) {
32-
$value = $callback();
33-
$this->fileAccess->setData($name, $value);
34-
return $value;
32+
try {
33+
$value = $callback();
34+
$this->fileAccess->setData($name, $value);
35+
return $value;
36+
}
37+
catch(CacheValueGenerationException) {
38+
return null;
39+
}
3540
}
3641
}
3742

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
namespace Gt\FileCache;
3+
4+
class CacheValueGenerationException extends FileCacheException {}

test/phpunit/CacheTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
namespace Gt\FileCache\Test;
33

44
use Gt\FileCache\Cache;
5+
use Gt\FileCache\CacheInvalidException;
6+
use Gt\FileCache\CacheValueGenerationException;
57
use Gt\FileCache\FileAccess;
8+
use Gt\FileCache\FileNotFoundException;
69
use PHPUnit\Framework\TestCase;
710
use SplFileInfo;
811
use SplFixedArray;
@@ -49,6 +52,52 @@ public function testGet_multipleCallsDoesNotCallbackMultipleTimes():void {
4952
self::assertSame(1, $count);
5053
}
5154

55+
public function testGet_nullValueCanBeCached():void {
56+
$sut = $this->getSut();
57+
$count = 0;
58+
59+
$callback = function()use(&$count):null {
60+
$count++;
61+
return null;
62+
};
63+
64+
self::assertNull($sut->get("test-null", $callback));
65+
self::assertNull($sut->get("test-null", $callback));
66+
self::assertSame(1, $count);
67+
}
68+
69+
public function testGet_generationExceptionDoesNotWriteInvalidValue():void {
70+
$fileAccess = self::createMock(FileAccess::class);
71+
$fileAccess->expects(self::once())
72+
->method("checkValidity")
73+
->with("test", 3600)
74+
->willThrowException(new FileNotFoundException("test"));
75+
$fileAccess->expects(self::never())
76+
->method("setData");
77+
78+
$sut = new Cache(fileAccess: $fileAccess);
79+
80+
self::assertNull($sut->get("test", function():never {
81+
throw new CacheValueGenerationException("Lookup failed");
82+
}));
83+
}
84+
85+
public function testGet_invalidCache_generationExceptionDoesNotWriteReplacement():void {
86+
$fileAccess = self::createMock(FileAccess::class);
87+
$fileAccess->expects(self::once())
88+
->method("checkValidity")
89+
->with("test", 3600)
90+
->willThrowException(new CacheInvalidException("test"));
91+
$fileAccess->expects(self::never())
92+
->method("setData");
93+
94+
$sut = new Cache(fileAccess: $fileAccess);
95+
96+
self::assertNull($sut->get("test", function():never {
97+
throw new CacheValueGenerationException("Lookup failed");
98+
}));
99+
}
100+
52101
public function testGetString():void {
53102
$value = uniqid();
54103
$sut = $this->getSut([

0 commit comments

Comments
 (0)