Skip to content

Commit d8eae65

Browse files
authored
Merge pull request #41 from programmatordev/OPA-45-improve-cache-handling
Improve cache handling
2 parents cc81cb4 + 545aff4 commit d8eae65

18 files changed

+197
-85
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "programmatordev/openweathermap-php-api",
33
"description": "OpenWeatherMap PHP library that provides convenient access to the OpenWeatherMap API",
44
"type": "library",
5-
"keywords": ["OpenWeatherMap", "API", "PHP", "SDK", "PSR-18", "PSR-17", "PSR-6", "PSR-3"],
5+
"keywords": ["OpenWeatherMap", "API", "PHP", "PHP8", "SDK", "PSR-18", "PSR-17", "PSR-6", "PSR-3"],
66
"license": "MIT",
77
"authors": [
88
{

docs/03-supported-apis.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,16 @@ $openWeatherMap->getWeather()
278278
#### `withCacheTtl`
279279

280280
```php
281-
withCacheTtl(?int $time): self
281+
withCacheTtl(int $seconds): self
282282
```
283283

284-
Makes a request and saves into cache with the provided time duration value (in seconds).
284+
Makes a request and saves into cache for the provided duration in seconds.
285+
286+
If `0` seconds is provided, the request will not be cached.
287+
288+
> **Note**
289+
> Setting cache to `0` seconds will **not** invalidate any existing cache.
290+
285291
Check the [Cache TTL](02-configuration.md#cache-ttl) section for more information regarding default values.
286292

287293
Available for all APIs if `cache` is enabled in the [configuration](02-configuration.md#cache).

docs/05-objects.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@
299299

300300
### AirPollutionLocationList
301301

302-
`getCoordinate()`: `float`
302+
`getCoordinate()`: [`Coordinate`](#coordinate)
303303

304304
`getList()`: [`AirPollution[]`](#airpollution)
305305

src/Endpoint/AbstractEndpoint.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class AbstractEndpoint
3939

4040
protected string $language;
4141

42-
protected ?int $cacheTtl = 60 * 10; // 10 minutes
42+
protected int $cacheTtl = 60 * 10; // 10 minutes
4343

4444
public function __construct(protected OpenWeatherMap $api)
4545
{
@@ -70,8 +70,12 @@ protected function sendRequest(
7070
{
7171
$this->configurePlugins();
7272

73-
$uri = $this->buildUrl($baseUrl, $query);
74-
$response = $this->httpClientBuilder->getHttpClient()->send($method, $uri, $headers, $body);
73+
$response = $this->httpClientBuilder->getHttpClient()->send(
74+
$method,
75+
$this->buildUrl($baseUrl, $query),
76+
$headers,
77+
$body
78+
);
7579

7680
if (($statusCode = $response->getStatusCode()) >= 400) {
7781
$this->handleResponseErrors($response, $statusCode);
@@ -84,14 +88,13 @@ private function configurePlugins(): void
8488
{
8589
// Plugin order is important
8690
// CachePlugin should come first, otherwise the LoggerPlugin will log requests even if they are cached
91+
8792
if ($this->cache !== null) {
8893
$this->httpClientBuilder->addPlugin(
8994
new CachePlugin($this->cache, $this->httpClientBuilder->getStreamFactory(), [
9095
'default_ttl' => $this->cacheTtl,
9196
'cache_lifetime' => 0,
92-
'cache_listeners' => ($this->logger !== null)
93-
? [new LoggerCacheListener($this->logger)]
94-
: []
97+
'cache_listeners' => ($this->logger !== null) ? [new LoggerCacheListener($this->logger)] : []
9598
])
9699
);
97100
}

src/Endpoint/GeocodingEndpoint.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class GeocodingEndpoint extends AbstractEndpoint
2626

2727
private string $urlGeocodingReverse = 'https://api.openweathermap.org/geo/1.0/reverse';
2828

29-
protected ?int $cacheTtl = 60 * 60 * 24 * 30; // 30 days
29+
protected int $cacheTtl = 60 * 60 * 24 * 30; // 30 days
3030

3131
/**
3232
* @return Location[]

src/Endpoint/Util/WithCacheTtlTrait.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
trait WithCacheTtlTrait
66
{
7-
public function withCacheTtl(?int $time): static
7+
public function withCacheTtl(int $seconds): static
88
{
99
$clone = clone $this;
10-
$clone->cacheTtl = $time;
10+
$clone->cacheTtl = $seconds;
1111

1212
return $clone;
1313
}
1414

15-
public function getCacheTtl(): ?int
15+
public function getCacheTtl(): int
1616
{
1717
return $this->cacheTtl;
1818
}

src/Test/AbstractTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected function setUp(): void
2121
$this->mockHttpClient = new Client();
2222
}
2323

24-
protected function getApi(): OpenWeatherMap
24+
protected function givenApi(): OpenWeatherMap
2525
{
2626
return new OpenWeatherMap(
2727
new Config([

tests/AbstractEndpointTest.php

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\OpenWeatherMap\Test;
4+
5+
use Nyholm\Psr7\Response;
6+
use ProgrammatorDev\OpenWeatherMap\Endpoint\AbstractEndpoint;
7+
use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap;
8+
use Psr\Cache\CacheItemPoolInterface;
9+
use Psr\Log\LoggerInterface;
10+
11+
class AbstractEndpointTest extends AbstractTest
12+
{
13+
public function testAbstractEndpointWithCache()
14+
{
15+
$this->mockHttpClient->addResponse(
16+
new Response(body: '[]')
17+
);
18+
19+
$cache = $this->createMock(CacheItemPoolInterface::class);
20+
$cache->expects($this->once())->method('save');
21+
22+
$api = $this->givenApi();
23+
$api->getConfig()->setCache($cache);
24+
25+
$this->mockSendRequest($api);
26+
}
27+
28+
public function testAbstractEndpointWithLogger()
29+
{
30+
$this->mockHttpClient->addResponse(
31+
new Response(body: '[]')
32+
);
33+
34+
$logger = $this->createMock(LoggerInterface::class);
35+
$logger->expects($this->atLeastOnce())->method('info');
36+
37+
$api = $this->givenApi();
38+
$api->getConfig()->setLogger($logger);
39+
40+
$this->mockSendRequest($api);
41+
}
42+
43+
private function mockSendRequest(OpenWeatherMap $api): void
44+
{
45+
// Using ReflectionClass to be able to call the *protected* sendRequest method
46+
// (otherwise it would not be possible)
47+
$endpoint = new AbstractEndpoint($api);
48+
$reflectionClass = new \ReflectionClass($endpoint);
49+
$sendRequest = $reflectionClass->getMethod('sendRequest');
50+
$sendRequest->invokeArgs($endpoint, ['GET', 'https://mock.test']);
51+
}
52+
}

tests/AirPollutionEndpointTest.php

+33-16
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44

55
use Nyholm\Psr7\Response;
66
use PHPUnit\Framework\Attributes\DataProviderExternal;
7+
use ProgrammatorDev\OpenWeatherMap\Endpoint\AirPollutionEndpoint;
78
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirPollution;
89
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirPollutionLocationList;
910
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirQuality;
1011
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirPollutionLocation;
1112
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate;
12-
use ProgrammatorDev\OpenWeatherMap\Entity\Location;
1313
use ProgrammatorDev\OpenWeatherMap\Test\DataProvider\InvalidParamDataProvider;
1414

1515
class AirPollutionEndpointTest extends AbstractTest
1616
{
17+
// --- CURRENT ---
18+
1719
public function testAirPollutionGetCurrent()
1820
{
1921
$this->mockHttpClient->addResponse(
@@ -23,17 +25,19 @@ public function testAirPollutionGetCurrent()
2325
)
2426
);
2527

26-
$response = $this->getApi()->getAirPollution()->getCurrent(38.7077507, -9.1365919);
28+
$response = $this->givenApi()->getAirPollution()->getCurrent(50, 50);
2729
$this->assertCurrentResponse($response);
2830
}
2931

3032
#[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidCoordinateData')]
3133
public function testAirPollutionGetCurrentWithInvalidCoordinate(float $latitude, float $longitude, string $expectedException)
3234
{
3335
$this->expectException($expectedException);
34-
$this->getApi()->getAirPollution()->getCurrent($latitude, $longitude);
36+
$this->givenApi()->getAirPollution()->getCurrent($latitude, $longitude);
3537
}
3638

39+
// --- FORECAST ---
40+
3741
public function testAirPollutionGetForecast()
3842
{
3943
$this->mockHttpClient->addResponse(
@@ -43,17 +47,19 @@ public function testAirPollutionGetForecast()
4347
)
4448
);
4549

46-
$response = $this->getApi()->getAirPollution()->getForecast(38.7077507, -9.1365919);
50+
$response = $this->givenApi()->getAirPollution()->getForecast(50, 50);
4751
$this->assertForecastResponse($response);
4852
}
4953

5054
#[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidCoordinateData')]
5155
public function testAirPollutionGetForecastWithInvalidCoordinate(float $latitude, float $longitude, string $expectedException)
5256
{
5357
$this->expectException($expectedException);
54-
$this->getApi()->getAirPollution()->getForecast($latitude, $longitude);
58+
$this->givenApi()->getAirPollution()->getForecast($latitude, $longitude);
5559
}
5660

61+
// --- HISTORY ---
62+
5763
public function testAirPollutionGetHistory()
5864
{
5965
$this->mockHttpClient->addResponse(
@@ -65,9 +71,9 @@ public function testAirPollutionGetHistory()
6571

6672
$utcTimezone = new \DateTimeZone('UTC');
6773

68-
$response = $this->getApi()->getAirPollution()->getHistory(
69-
38.7077507,
70-
-9.1365919,
74+
$response = $this->givenApi()->getAirPollution()->getHistory(
75+
50,
76+
50,
7177
new \DateTimeImmutable('-5 days', $utcTimezone),
7278
new \DateTimeImmutable('-4 days', $utcTimezone)
7379
);
@@ -82,7 +88,7 @@ public function testAirPollutionGetHistoryWithInvalidCoordinate(float $latitude,
8288
$startDate = new \DateTimeImmutable('-5 days');
8389
$endDate = new \DateTimeImmutable('-4 days');
8490

85-
$this->getApi()->getAirPollution()->getHistory($latitude, $longitude, $startDate, $endDate);
91+
$this->givenApi()->getAirPollution()->getHistory($latitude, $longitude, $startDate, $endDate);
8692
}
8793

8894
#[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidPastDateData')]
@@ -92,9 +98,9 @@ public function testAirPollutionGetHistoryWithInvalidPastStartDate(
9298
)
9399
{
94100
$this->expectException($expectedException);
95-
$this->getApi()->getAirPollution()->getHistory(
96-
38.7077507,
97-
-9.1365919,
101+
$this->givenApi()->getAirPollution()->getHistory(
102+
50,
103+
50,
98104
$startDate,
99105
new \DateTimeImmutable('-5 days', new \DateTimeZone('UTC'))
100106
);
@@ -107,9 +113,9 @@ public function testAirPollutionGetHistoryWithInvalidPastEndDate(
107113
)
108114
{
109115
$this->expectException($expectedException);
110-
$this->getApi()->getAirPollution()->getHistory(
111-
38.7077507,
112-
-9.1365919,
116+
$this->givenApi()->getAirPollution()->getHistory(
117+
50,
118+
50,
113119
new \DateTimeImmutable('-5 days', new \DateTimeZone('UTC')),
114120
$endDate
115121
);
@@ -123,9 +129,20 @@ public function testAirPollutionGetHistoryWithInvalidDateRange(
123129
)
124130
{
125131
$this->expectException($expectedException);
126-
$this->getApi()->getAirPollution()->getHistory(38.7077507, -9.1365919, $startDate, $endDate);
132+
$this->givenApi()->getAirPollution()->getHistory(50, 50, $startDate, $endDate);
133+
}
134+
135+
// --- ASSERT METHODS EXIST ---
136+
137+
public function testAirPollutionMethodsExist()
138+
{
139+
$this->assertSame(false, method_exists(AirPollutionEndpoint::class, 'withUnitSystem'));
140+
$this->assertSame(false, method_exists(AirPollutionEndpoint::class, 'withLanguage'));
141+
$this->assertSame(true, method_exists(AirPollutionEndpoint::class, 'withCacheTtl'));
127142
}
128143

144+
// --- ASSERT RESPONSES ---
145+
129146
private function assertCurrentResponse(AirPollutionLocation $response): void
130147
{
131148
$this->assertInstanceOf(AirPollutionLocation::class, $response);

tests/ApiErrorTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function testApiError(int $statusCode, string $expectedException)
2424
);
2525

2626
$this->expectException($expectedException);
27-
$this->getApi()->getWeather()->getCurrent(38.7077507, -9.1365919);
27+
$this->givenApi()->getWeather()->getCurrent(38.7077507, -9.1365919);
2828
}
2929

3030
public static function provideApiErrorData(): \Generator

tests/ConfigTest.php

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace ProgrammatorDev\OpenWeatherMap\Test;
44

5-
use Monolog\Logger;
65
use PHPUnit\Framework\Attributes\DataProvider;
76
use PHPUnit\Framework\Attributes\DataProviderExternal;
87
use ProgrammatorDev\OpenWeatherMap\Config;
@@ -11,7 +10,6 @@
1110
use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException;
1211
use Psr\Cache\CacheItemPoolInterface;
1312
use Psr\Log\LoggerInterface;
14-
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
1513
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
1614
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
1715

@@ -45,8 +43,8 @@ public function testConfigWithOptions()
4543
'unitSystem' => 'imperial',
4644
'language' => 'pt',
4745
'httpClientBuilder' => new HttpClientBuilder(),
48-
'cache' => new FilesystemAdapter(),
49-
'logger' => new Logger('test')
46+
'cache' => $this->createMock(CacheItemPoolInterface::class),
47+
'logger' => $this->createMock(LoggerInterface::class)
5048
]);
5149

5250
$this->assertSame('newtestappkey', $config->getApplicationKey());
@@ -139,13 +137,13 @@ public function testConfigSetHttpClientBuilder()
139137

140138
public function testConfigSetCache()
141139
{
142-
$this->config->setCache(new FilesystemAdapter());
140+
$this->config->setCache($this->createMock(CacheItemPoolInterface::class));
143141
$this->assertInstanceOf(CacheItemPoolInterface::class, $this->config->getCache());
144142
}
145143

146144
public function testConfigSetLogger()
147145
{
148-
$this->config->setLogger(new Logger('test'));
146+
$this->config->setLogger($this->createMock(LoggerInterface::class));
149147
$this->assertInstanceOf(LoggerInterface::class, $this->config->getLogger());
150148
}
151149
}

0 commit comments

Comments
 (0)