Skip to content

Commit

Permalink
Merge pull request #222 from akeneo/api-1694
Browse files Browse the repository at this point in the history
feat(api-1694): enable cache on resources
  • Loading branch information
tfehringer authored Feb 15, 2022
2 parents 0d9c9f8 + b24a16d commit cf65304
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 15 deletions.
56 changes: 41 additions & 15 deletions src/AkeneoPimClientBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
use Akeneo\Pim\ApiClient\Api\ProductApi;
use Akeneo\Pim\ApiClient\Api\ProductMediaFileApi;
use Akeneo\Pim\ApiClient\Api\ProductModelApi;
use Akeneo\Pim\ApiClient\Cache\LRUCache;
use Akeneo\Pim\ApiClient\Client\AuthenticatedHttpClient;
use Akeneo\Pim\ApiClient\Client\CachedResourceClient;
use Akeneo\Pim\ApiClient\Client\HttpClient;
use Akeneo\Pim\ApiClient\Client\ResourceClient;
use Akeneo\Pim\ApiClient\FileSystem\FileSystemInterface;
Expand Down Expand Up @@ -60,6 +62,8 @@ class AkeneoPimClientBuilder
/** @var FileSystemInterface */
protected $fileSystem;

protected bool $cacheEnabled = false;

/**
* @param string $baseUri Base uri to request the API
*/
Expand Down Expand Up @@ -146,6 +150,28 @@ public function buildAuthenticatedByToken(string $clientId, string $secret, stri
return $this->buildAuthenticatedClient($authentication);
}

/**
* Enable Caching
* Disabled by default
*/
public function enableCache(): self
{
$this->cacheEnabled = true;

return $this;
}

/**
* Disable Caching
* Disabled by default
*/
public function disableCache(): self
{
$this->cacheEnabled = false;

return $this;
}

/**
* @param Authentication $authentication
*
Expand All @@ -155,26 +181,26 @@ protected function buildAuthenticatedClient(Authentication $authentication): Ake
{
[$resourceClient, $pageFactory, $cursorFactory, $fileSystem] = $this->setUp($authentication);

$client = new AkeneoPimClient(
$resourceClientWithCache = !$this->cacheEnabled ? $resourceClient : new CachedResourceClient($resourceClient, new Cache());

return new AkeneoPimClient(
$authentication,
new ProductApi($resourceClient, $pageFactory, $cursorFactory),
new CategoryApi($resourceClient, $pageFactory, $cursorFactory),
new AttributeApi($resourceClient, $pageFactory, $cursorFactory),
new AttributeOptionApi($resourceClient, $pageFactory, $cursorFactory),
new AttributeGroupApi($resourceClient, $pageFactory, $cursorFactory),
new FamilyApi($resourceClient, $pageFactory, $cursorFactory),
new CategoryApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new AttributeApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new AttributeOptionApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new AttributeGroupApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new FamilyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new ProductMediaFileApi($resourceClient, $pageFactory, $cursorFactory, $fileSystem),
new LocaleApi($resourceClient, $pageFactory, $cursorFactory),
new ChannelApi($resourceClient, $pageFactory, $cursorFactory),
new CurrencyApi($resourceClient, $pageFactory, $cursorFactory),
new MeasureFamilyApi($resourceClient, $pageFactory, $cursorFactory),
new MeasurementFamilyApi($resourceClient),
new AssociationTypeApi($resourceClient, $pageFactory, $cursorFactory),
new FamilyVariantApi($resourceClient, $pageFactory, $cursorFactory),
new LocaleApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new ChannelApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new CurrencyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new MeasureFamilyApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new MeasurementFamilyApi($resourceClientWithCache),
new AssociationTypeApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new FamilyVariantApi($resourceClientWithCache, $pageFactory, $cursorFactory),
new ProductModelApi($resourceClient, $pageFactory, $cursorFactory)
);

return $client;
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/Cache/CacheInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Akeneo\Pim\ApiClient\Cache;

interface CacheInterface
{
/**
* @param string $key Key of the cached resource
*
* @return array|null Return the cached resource, null if the resource was not found.
*/
public function get(string $key): ?array;

/**
* @param string $key Key of the cached resource
* @param mixed $value The cached resource
*
* @return void
*/
public function set(string $key, $value): void;
}
56 changes: 56 additions & 0 deletions src/Cache/LRUCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Akeneo\Pim\ApiClient\Cache;

class LRUCache implements CacheInterface
{
private int $maxItems;
private array $items = [];

/**
* @param int $maxItems Maximum number of allowed cache items.
*/
public function __construct(int $maxItems = 100)
{
$this->maxItems = $maxItems;
}

/**
* {@inheritdoc}
*/
public function get(string $key): ?array
{
if (!isset($this->items[$key])) {
return null;
}

$entry = $this->items[$key];

unset($this->items[$key]);
$this->items[$key] = $entry;

return $entry;
}

/**
* {@inheritdoc}
*/
public function set(string $key, $value): void
{
$this->items[$key] = $value;

$diff = count($this->items) - $this->maxItems;

if ($diff <= 0) {
return;
}

reset($this->items);
for ($i = 0; $i < $diff; $i++) {
unset($this->items[key($this->items)]);
next($this->items);
}
}
}
103 changes: 103 additions & 0 deletions src/Client/CachedResourceClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace Akeneo\Pim\ApiClient\Client;

use Akeneo\Pim\ApiClient\Cache\CacheInterface;
use Psr\Http\Message\ResponseInterface;

class CachedResourceClient implements ResourceClientInterface
{
private ResourceClientInterface $resourceClient;
private CacheInterface $cache;

public function __construct(
ResourceClientInterface $resourceClient,
CacheInterface $cache
) {
$this->resourceClient = $resourceClient;
$this->cache = $cache;
}

/**
* {@inheritdoc}
*/
public function getResource(string $uri, array $uriParameters = [], array $queryParameters = []): array
{
$cacheKey = md5($uri.implode('', $uriParameters));

if ($cachedItem = $this->cache->get($cacheKey)) {
return $cachedItem;
}

$resource = $this->resourceClient->getResource($uri, $uriParameters, $queryParameters);
$this->cache->set($cacheKey, $resource);

return $resource;
}

/**
* {@inheritdoc}
*/
public function getResources(string $uri, array $uriParameters = [], ?int $limit = 10, ?bool $withCount = false, array $queryParameters = []): array
{
return $this->resourceClient->getResources($uri, $uriParameters, $limit, $withCount, $queryParameters);
}

/**
* {@inheritdoc}
*/
public function createResource(string $uri, array $uriParameters = [], array $body = []): int
{
return $this->resourceClient->createResource($uri, $uriParameters, $body);
}

/**
* {@inheritdoc}
*/
public function createMultipartResource(string $uri, array $uriParameters = [], array $requestParts = []): ResponseInterface
{
return $this->resourceClient->createMultipartResource($uri, $uriParameters, $requestParts);
}

/**
* {@inheritdoc}
*/
public function upsertResource(string $uri, array $uriParameters = [], array $body = []): int
{
return $this->resourceClient->upsertResource($uri, $uriParameters, $body);
}

/**
* {@inheritdoc}
*/
public function upsertStreamResourceList(string $uri, array $uriParameters = [], $resources = []): \Traversable
{
return $this->resourceClient->upsertStreamResourceList($uri, $uriParameters, $resources);
}

/**
* {@inheritdoc}
*/
public function upsertJsonResourceList(string $uri, array $uriParameters = [], array $resources = []): array
{
return $this->resourceClient->upsertJsonResourceList($uri, $uriParameters, $resources);
}

/**
* {@inheritdoc}
*/
public function deleteResource(string $uri, array $uriParameters = []): int
{
return $this->resourceClient->deleteResource($uri, $uriParameters);
}

/**
* {@inheritdoc}
*/
public function getStreamedResource(string $uri, array $uriParameters = []): ResponseInterface
{
return $this->resourceClient->getStreamedResource($uri, $uriParameters);
}
}
42 changes: 42 additions & 0 deletions tests/Cache/LRUCacheTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Akeneo\Pim\ApiClient\tests\Cache;

use Akeneo\Pim\ApiClient\Cache\LRUCache;
use PHPUnit\Framework\TestCase;

class LRUCacheTest extends TestCase
{
public function testLimitsSize(): void
{
$cache = new LRUCache(3);
$cache->set('a', [1]);
$cache->set('b', [2]);
$cache->set('c', [3]);
$cache->set('d', [4]);
$cache->set('e', [5]);
$this->assertNull($cache->get('a'));
$this->assertNull($cache->get('b'));
$this->assertSame([3], $cache->get('c'));
$this->assertSame([4], $cache->get('d'));
$this->assertSame([5], $cache->get('e'));
}

public function testRemovesLru(): void
{
$cache = new LRUCache(3);
$cache->set('a', [1]);
$cache->set('b', [2]);
$cache->set('c', [3]);
$cache->get('a'); // Puts a back on the end
$cache->set('d', [4]);
$cache->set('e', [5]);
$this->assertNull($cache->get('b'));
$this->assertNull($cache->get('c'));
$this->assertSame([1], $cache->get('a'));
$this->assertSame([4], $cache->get('d'));
$this->assertSame([5], $cache->get('e'));
}
}
45 changes: 45 additions & 0 deletions tests/Client/CachedResourceClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Akeneo\Pim\ApiClient\tests\Client;

use Akeneo\Pim\ApiClient\Cache\CacheInterface;
use Akeneo\Pim\ApiClient\Client\CachedResourceClient;
use Akeneo\Pim\ApiClient\Client\ResourceClient;
use Akeneo\Pim\ApiClient\tests\Api\ApiTestCase;

class CachedResourceClientTest extends ApiTestCase
{
public function test_get_cached_resource(): void
{
$resourceClient = $this->createMock(ResourceClient::class);
$mockCache = $this->createMock(CacheInterface::class);

$uri = 'uri';
$uriParameters = ['uriParameter'];

$cacheKey = md5($uri.implode('', $uriParameters));

$mockCache
->expects(self::exactly(2))
->method('get')
->with($cacheKey)
->willReturnOnConsecutiveCalls(null, ['cachedValue']);

$resourceClient
->expects(self::once())
->method('getResource')
->with($uri, $uriParameters)->willReturn(['resource']);

$mockCache
->expects(self::once())
->method('set')
->with($cacheKey, ['resource']);

$cachedResourceClient = new CachedResourceClient($resourceClient, $mockCache);

self::assertSame(['resource'], $cachedResourceClient->getResource($uri, $uriParameters));
self::assertSame(['cachedValue'], $cachedResourceClient->getResource($uri, $uriParameters));
}
}

0 comments on commit cf65304

Please sign in to comment.