Skip to content

Commit b24a16d

Browse files
dFayetThomas Fehringer
authored andcommitted
feat(api-1694): enable cache on resources
1 parent 0d9c9f8 commit b24a16d

File tree

6 files changed

+310
-15
lines changed

6 files changed

+310
-15
lines changed

src/AkeneoPimClientBuilder.php

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use Akeneo\Pim\ApiClient\Api\ProductApi;
1919
use Akeneo\Pim\ApiClient\Api\ProductMediaFileApi;
2020
use Akeneo\Pim\ApiClient\Api\ProductModelApi;
21+
use Akeneo\Pim\ApiClient\Cache\LRUCache;
2122
use Akeneo\Pim\ApiClient\Client\AuthenticatedHttpClient;
23+
use Akeneo\Pim\ApiClient\Client\CachedResourceClient;
2224
use Akeneo\Pim\ApiClient\Client\HttpClient;
2325
use Akeneo\Pim\ApiClient\Client\ResourceClient;
2426
use Akeneo\Pim\ApiClient\FileSystem\FileSystemInterface;
@@ -60,6 +62,8 @@ class AkeneoPimClientBuilder
6062
/** @var FileSystemInterface */
6163
protected $fileSystem;
6264

65+
protected bool $cacheEnabled = false;
66+
6367
/**
6468
* @param string $baseUri Base uri to request the API
6569
*/
@@ -146,6 +150,28 @@ public function buildAuthenticatedByToken(string $clientId, string $secret, stri
146150
return $this->buildAuthenticatedClient($authentication);
147151
}
148152

153+
/**
154+
* Enable Caching
155+
* Disabled by default
156+
*/
157+
public function enableCache(): self
158+
{
159+
$this->cacheEnabled = true;
160+
161+
return $this;
162+
}
163+
164+
/**
165+
* Disable Caching
166+
* Disabled by default
167+
*/
168+
public function disableCache(): self
169+
{
170+
$this->cacheEnabled = false;
171+
172+
return $this;
173+
}
174+
149175
/**
150176
* @param Authentication $authentication
151177
*
@@ -155,26 +181,26 @@ protected function buildAuthenticatedClient(Authentication $authentication): Ake
155181
{
156182
[$resourceClient, $pageFactory, $cursorFactory, $fileSystem] = $this->setUp($authentication);
157183

158-
$client = new AkeneoPimClient(
184+
$resourceClientWithCache = !$this->cacheEnabled ? $resourceClient : new CachedResourceClient($resourceClient, new Cache());
185+
186+
return new AkeneoPimClient(
159187
$authentication,
160188
new ProductApi($resourceClient, $pageFactory, $cursorFactory),
161-
new CategoryApi($resourceClient, $pageFactory, $cursorFactory),
162-
new AttributeApi($resourceClient, $pageFactory, $cursorFactory),
163-
new AttributeOptionApi($resourceClient, $pageFactory, $cursorFactory),
164-
new AttributeGroupApi($resourceClient, $pageFactory, $cursorFactory),
165-
new FamilyApi($resourceClient, $pageFactory, $cursorFactory),
189+
new CategoryApi($resourceClientWithCache, $pageFactory, $cursorFactory),
190+
new AttributeApi($resourceClientWithCache, $pageFactory, $cursorFactory),
191+
new AttributeOptionApi($resourceClientWithCache, $pageFactory, $cursorFactory),
192+
new AttributeGroupApi($resourceClientWithCache, $pageFactory, $cursorFactory),
193+
new FamilyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
166194
new ProductMediaFileApi($resourceClient, $pageFactory, $cursorFactory, $fileSystem),
167-
new LocaleApi($resourceClient, $pageFactory, $cursorFactory),
168-
new ChannelApi($resourceClient, $pageFactory, $cursorFactory),
169-
new CurrencyApi($resourceClient, $pageFactory, $cursorFactory),
170-
new MeasureFamilyApi($resourceClient, $pageFactory, $cursorFactory),
171-
new MeasurementFamilyApi($resourceClient),
172-
new AssociationTypeApi($resourceClient, $pageFactory, $cursorFactory),
173-
new FamilyVariantApi($resourceClient, $pageFactory, $cursorFactory),
195+
new LocaleApi($resourceClientWithCache, $pageFactory, $cursorFactory),
196+
new ChannelApi($resourceClientWithCache, $pageFactory, $cursorFactory),
197+
new CurrencyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
198+
new MeasureFamilyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
199+
new MeasurementFamilyApi($resourceClientWithCache),
200+
new AssociationTypeApi($resourceClientWithCache, $pageFactory, $cursorFactory),
201+
new FamilyVariantApi($resourceClientWithCache, $pageFactory, $cursorFactory),
174202
new ProductModelApi($resourceClient, $pageFactory, $cursorFactory)
175203
);
176-
177-
return $client;
178204
}
179205

180206
/**

src/Cache/CacheInterface.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Akeneo\Pim\ApiClient\Cache;
6+
7+
interface CacheInterface
8+
{
9+
/**
10+
* @param string $key Key of the cached resource
11+
*
12+
* @return array|null Return the cached resource, null if the resource was not found.
13+
*/
14+
public function get(string $key): ?array;
15+
16+
/**
17+
* @param string $key Key of the cached resource
18+
* @param mixed $value The cached resource
19+
*
20+
* @return void
21+
*/
22+
public function set(string $key, $value): void;
23+
}

src/Cache/LRUCache.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Akeneo\Pim\ApiClient\Cache;
6+
7+
class LRUCache implements CacheInterface
8+
{
9+
private int $maxItems;
10+
private array $items = [];
11+
12+
/**
13+
* @param int $maxItems Maximum number of allowed cache items.
14+
*/
15+
public function __construct(int $maxItems = 100)
16+
{
17+
$this->maxItems = $maxItems;
18+
}
19+
20+
/**
21+
* {@inheritdoc}
22+
*/
23+
public function get(string $key): ?array
24+
{
25+
if (!isset($this->items[$key])) {
26+
return null;
27+
}
28+
29+
$entry = $this->items[$key];
30+
31+
unset($this->items[$key]);
32+
$this->items[$key] = $entry;
33+
34+
return $entry;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function set(string $key, $value): void
41+
{
42+
$this->items[$key] = $value;
43+
44+
$diff = count($this->items) - $this->maxItems;
45+
46+
if ($diff <= 0) {
47+
return;
48+
}
49+
50+
reset($this->items);
51+
for ($i = 0; $i < $diff; $i++) {
52+
unset($this->items[key($this->items)]);
53+
next($this->items);
54+
}
55+
}
56+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Akeneo\Pim\ApiClient\Client;
6+
7+
use Akeneo\Pim\ApiClient\Cache\CacheInterface;
8+
use Psr\Http\Message\ResponseInterface;
9+
10+
class CachedResourceClient implements ResourceClientInterface
11+
{
12+
private ResourceClientInterface $resourceClient;
13+
private CacheInterface $cache;
14+
15+
public function __construct(
16+
ResourceClientInterface $resourceClient,
17+
CacheInterface $cache
18+
) {
19+
$this->resourceClient = $resourceClient;
20+
$this->cache = $cache;
21+
}
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function getResource(string $uri, array $uriParameters = [], array $queryParameters = []): array
27+
{
28+
$cacheKey = md5($uri.implode('', $uriParameters));
29+
30+
if ($cachedItem = $this->cache->get($cacheKey)) {
31+
return $cachedItem;
32+
}
33+
34+
$resource = $this->resourceClient->getResource($uri, $uriParameters, $queryParameters);
35+
$this->cache->set($cacheKey, $resource);
36+
37+
return $resource;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function getResources(string $uri, array $uriParameters = [], ?int $limit = 10, ?bool $withCount = false, array $queryParameters = []): array
44+
{
45+
return $this->resourceClient->getResources($uri, $uriParameters, $limit, $withCount, $queryParameters);
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function createResource(string $uri, array $uriParameters = [], array $body = []): int
52+
{
53+
return $this->resourceClient->createResource($uri, $uriParameters, $body);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function createMultipartResource(string $uri, array $uriParameters = [], array $requestParts = []): ResponseInterface
60+
{
61+
return $this->resourceClient->createMultipartResource($uri, $uriParameters, $requestParts);
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function upsertResource(string $uri, array $uriParameters = [], array $body = []): int
68+
{
69+
return $this->resourceClient->upsertResource($uri, $uriParameters, $body);
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function upsertStreamResourceList(string $uri, array $uriParameters = [], $resources = []): \Traversable
76+
{
77+
return $this->resourceClient->upsertStreamResourceList($uri, $uriParameters, $resources);
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function upsertJsonResourceList(string $uri, array $uriParameters = [], array $resources = []): array
84+
{
85+
return $this->resourceClient->upsertJsonResourceList($uri, $uriParameters, $resources);
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function deleteResource(string $uri, array $uriParameters = []): int
92+
{
93+
return $this->resourceClient->deleteResource($uri, $uriParameters);
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function getStreamedResource(string $uri, array $uriParameters = []): ResponseInterface
100+
{
101+
return $this->resourceClient->getStreamedResource($uri, $uriParameters);
102+
}
103+
}

tests/Cache/LRUCacheTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Akeneo\Pim\ApiClient\tests\Cache;
6+
7+
use Akeneo\Pim\ApiClient\Cache\LRUCache;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class LRUCacheTest extends TestCase
11+
{
12+
public function testLimitsSize(): void
13+
{
14+
$cache = new LRUCache(3);
15+
$cache->set('a', [1]);
16+
$cache->set('b', [2]);
17+
$cache->set('c', [3]);
18+
$cache->set('d', [4]);
19+
$cache->set('e', [5]);
20+
$this->assertNull($cache->get('a'));
21+
$this->assertNull($cache->get('b'));
22+
$this->assertSame([3], $cache->get('c'));
23+
$this->assertSame([4], $cache->get('d'));
24+
$this->assertSame([5], $cache->get('e'));
25+
}
26+
27+
public function testRemovesLru(): void
28+
{
29+
$cache = new LRUCache(3);
30+
$cache->set('a', [1]);
31+
$cache->set('b', [2]);
32+
$cache->set('c', [3]);
33+
$cache->get('a'); // Puts a back on the end
34+
$cache->set('d', [4]);
35+
$cache->set('e', [5]);
36+
$this->assertNull($cache->get('b'));
37+
$this->assertNull($cache->get('c'));
38+
$this->assertSame([1], $cache->get('a'));
39+
$this->assertSame([4], $cache->get('d'));
40+
$this->assertSame([5], $cache->get('e'));
41+
}
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Akeneo\Pim\ApiClient\tests\Client;
6+
7+
use Akeneo\Pim\ApiClient\Cache\CacheInterface;
8+
use Akeneo\Pim\ApiClient\Client\CachedResourceClient;
9+
use Akeneo\Pim\ApiClient\Client\ResourceClient;
10+
use Akeneo\Pim\ApiClient\tests\Api\ApiTestCase;
11+
12+
class CachedResourceClientTest extends ApiTestCase
13+
{
14+
public function test_get_cached_resource(): void
15+
{
16+
$resourceClient = $this->createMock(ResourceClient::class);
17+
$mockCache = $this->createMock(CacheInterface::class);
18+
19+
$uri = 'uri';
20+
$uriParameters = ['uriParameter'];
21+
22+
$cacheKey = md5($uri.implode('', $uriParameters));
23+
24+
$mockCache
25+
->expects(self::exactly(2))
26+
->method('get')
27+
->with($cacheKey)
28+
->willReturnOnConsecutiveCalls(null, ['cachedValue']);
29+
30+
$resourceClient
31+
->expects(self::once())
32+
->method('getResource')
33+
->with($uri, $uriParameters)->willReturn(['resource']);
34+
35+
$mockCache
36+
->expects(self::once())
37+
->method('set')
38+
->with($cacheKey, ['resource']);
39+
40+
$cachedResourceClient = new CachedResourceClient($resourceClient, $mockCache);
41+
42+
self::assertSame(['resource'], $cachedResourceClient->getResource($uri, $uriParameters));
43+
self::assertSame(['cachedValue'], $cachedResourceClient->getResource($uri, $uriParameters));
44+
}
45+
}

0 commit comments

Comments
 (0)