Skip to content

Commit

Permalink
Improve AbstractCacheProvider (#40)
Browse files Browse the repository at this point in the history
* **BREAKING CHANGES**

- Rename interfaces to have the Interface suffix

**OTHER CHANGES**

- Add pre-projection callables to `AbstractCachedProvider::getAndCacheData` to allow to perform additional operations after fetching data when there is a cache miss

- Add `invalidateCacheItemByKey` to `AbstractCachedProvider` to allow invalidation of individual cache items

- Fix tests

- Add new test cases for the new methods

- Update documention

* Rework logic of pre-projection callables

Fix tests

Update Documentation
  • Loading branch information
hugo-goncalves-kununu authored Jan 16, 2023
1 parent 0f0431d commit 4fc1e44
Show file tree
Hide file tree
Showing 45 changed files with 521 additions and 330 deletions.
311 changes: 195 additions & 116 deletions README.md

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"symfony/browser-kit": "^4.4",
"symfony/cache": "^4.4",
"jms/serializer-bundle": "^3.8",
"kununu/testing-bundle": "^15.0",
"kununu/testing-bundle": "^16.0",
"kununu/scripts": "*"
},
"suggest": {
Expand All @@ -30,5 +30,18 @@
"psr-4": {
"Kununu\\Projections\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit --no-coverage tests",
"test-coverage": "phpunit --coverage-clover tests/.results/coverage.xml --coverage-html tests/.results/html/ tests"
},
"scripts-descriptions": {
"test": "Run all tests",
"test-coverage": "Run all tests with coverage report"
},
"config": {
"allow-plugins": {
"kununu/scripts": true
}
}
}
6 changes: 3 additions & 3 deletions src/CacheCleaner/AbstractCacheCleanerByTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

namespace Kununu\Projections\CacheCleaner;

use Kununu\Projections\ProjectionRepository;
use Kununu\Projections\ProjectionRepositoryInterface;
use Kununu\Projections\Tag\Tags;
use Psr\Log\LoggerInterface;

abstract class AbstractCacheCleanerByTags implements CacheCleaner
abstract class AbstractCacheCleanerByTags implements CacheCleanerInterface
{
private $projectionRepository;
private $logger;

public function __construct(ProjectionRepository $projectionRepository, LoggerInterface $logger)
public function __construct(ProjectionRepositoryInterface $projectionRepository, LoggerInterface $logger)
{
$this->projectionRepository = $projectionRepository;
$this->logger = $logger;
Expand Down
4 changes: 2 additions & 2 deletions src/CacheCleaner/CacheCleanerChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

namespace Kununu\Projections\CacheCleaner;

final class CacheCleanerChain implements CacheCleaner
final class CacheCleanerChain implements CacheCleanerInterface
{
private $cacheCleaners;

public function __construct(CacheCleaner ...$cacheCleaners)
public function __construct(CacheCleanerInterface ...$cacheCleaners)
{
$this->cacheCleaners = $cacheCleaners;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Kununu\Projections\CacheCleaner;

interface CacheCleaner
interface CacheCleanerInterface
{
public function clear(): void;
}
3 changes: 2 additions & 1 deletion src/Exception/ProjectionException.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections\Exception;

Expand Down
5 changes: 3 additions & 2 deletions src/ProjectionItem.php → src/ProjectionItemInterface.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections;

use Kununu\Projections\Tag\Tags;

interface ProjectionItem
interface ProjectionItemInterface
{
public function getKey(): string;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections;

interface ProjectionItemIterable extends ProjectionItem
interface ProjectionItemIterableInterface extends ProjectionItemInterface
{
public function storeData(iterable $data): ProjectionItemIterable;
public function storeData(iterable $data): ProjectionItemIterableInterface;

public function data(): iterable;
}
11 changes: 5 additions & 6 deletions src/ProjectionItemIterableTrait.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections;

Expand All @@ -9,9 +10,9 @@ trait ProjectionItemIterableTrait
/** @var array */
private $data;

public function storeData(iterable $data): ProjectionItemIterable
public function storeData(iterable $data): ProjectionItemIterableInterface
{
if ($this instanceof ProjectionItemIterable) {
if ($this instanceof ProjectionItemIterableInterface) {
if (is_array($data)) {
$this->data = $data;
} else {
Expand All @@ -23,9 +24,7 @@ public function storeData(iterable $data): ProjectionItemIterable
return $this;
}

throw new BadMethodCallException(
sprintf('Class using this trait must be a %s', ProjectionItemIterable::class)
);
throw new BadMethodCallException(sprintf('Class using this trait must be a %s', ProjectionItemIterableInterface::class));
}

public function data(): iterable
Expand Down
20 changes: 0 additions & 20 deletions src/ProjectionRepository.php

This file was deleted.

21 changes: 21 additions & 0 deletions src/ProjectionRepositoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);

namespace Kununu\Projections;

use Kununu\Projections\Tag\Tags;

interface ProjectionRepositoryInterface
{
public function add(ProjectionItemInterface $item): void;

public function addDeferred(ProjectionItemInterface $item): void;

public function flush(): void;

public function get(ProjectionItemInterface $item): ?ProjectionItemInterface;

public function delete(ProjectionItemInterface $item): void;

public function deleteByTags(Tags $tags): void;
}
52 changes: 41 additions & 11 deletions src/Provider/AbstractCachedProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace Kununu\Projections\Provider;

use Kununu\Projections\ProjectionItemIterable;
use Kununu\Projections\ProjectionRepository;
use Kununu\Projections\ProjectionItemIterableInterface;
use Kununu\Projections\ProjectionRepositoryInterface;
use Psr\Log\LoggerInterface;

abstract class AbstractCachedProvider
Expand All @@ -15,18 +15,21 @@ abstract class AbstractCachedProvider
private $projectionRepository;
private $logger;

public function __construct(ProjectionRepository $projectionRepository, LoggerInterface $logger)
public function __construct(ProjectionRepositoryInterface $projectionRepository, LoggerInterface $logger)
{
$this->projectionRepository = $projectionRepository;
$this->logger = $logger;
}

protected function getAndCacheData(ProjectionItemIterable $item, callable $dataGetter): ?iterable
{
protected function getAndCacheData(
ProjectionItemIterableInterface $item,
callable $dataGetter,
callable ...$preProjections
): ?iterable {
$this->logger->info('Getting data from cache', [self::CACHE_KEY => $item->getKey()]);

$projectedItem = $this->projectionRepository->get($item);
if ($projectedItem instanceof ProjectionItemIterable) {
if ($projectedItem instanceof ProjectionItemIterableInterface) {
$this->logger->info(
'Item hit! Returning data from the cache',
[
Expand All @@ -39,16 +42,43 @@ protected function getAndCacheData(ProjectionItemIterable $item, callable $dataG
}

$this->logger->info('Item not hit! Fetching data...', [self::CACHE_KEY => $item->getKey()]);

$data = $dataGetter();

if (is_iterable($data)) {
$this->projectionRepository->add($item->storeData($data));
$this->logger->info('Item saved into cache and returned', [self::CACHE_KEY => $item->getKey(), self::DATA => $data]);
// Manipulate data before projection if callers are defined
foreach ($preProjections as $preProjection) {
$data = $preProjection($item, $data);
// If pre-projection callable returns null means we do not have relevant information.
// We will not store the item in the cache and will break the pre-projection chain
if (null === $data) {
$this->logger->info('Item not stored in the cache!', [self::DATA => $data]);
$data = null;

break;
}
}

return $data;
if (null !== $data) {
$this->projectionRepository->add($item->storeData($data));
$this->logger->info(
'Item saved into cache and returned',
[self::CACHE_KEY => $item->getKey(), self::DATA => $data]
);
}
} else {
$this->logger->info('No data fetched and stored into cache!', [self::CACHE_KEY => $item->getKey()]);
$data = null;
}

$this->logger->info('No data fetched and stored into cache!', [self::CACHE_KEY => $item->getKey()]);
return $data;
}

protected function invalidateCacheItemByKey(ProjectionItemIterableInterface $projectionItem): self
{
$this->logger->info('Deleting cache item', ['cache_key' => $projectionItem->getKey()]);
$this->projectionRepository->delete($projectionItem);

return null;
return $this;
}
}
23 changes: 12 additions & 11 deletions src/Repository/CachePoolProjectionRepository.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections\Repository;

use JMS\Serializer\SerializerInterface;
use Kununu\Projections\Exception\ProjectionException;
use Kununu\Projections\ProjectionItem;
use Kununu\Projections\ProjectionRepository;
use Kununu\Projections\ProjectionItemInterface;
use Kununu\Projections\ProjectionRepositoryInterface;
use Kununu\Projections\Tag\Tags;
use JMS\Serializer\SerializerInterface;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;

final class CachePoolProjectionRepository implements ProjectionRepository
final class CachePoolProjectionRepository implements ProjectionRepositoryInterface
{
private const JMS_SERIALIZER_FORMAT = 'json';

Expand All @@ -19,11 +20,11 @@ final class CachePoolProjectionRepository implements ProjectionRepository

public function __construct(TagAwareAdapterInterface $cachePool, SerializerInterface $serializer)
{
$this->cachePool = $cachePool;
$this->cachePool = $cachePool;
$this->serializer = $serializer;
}

public function add(ProjectionItem $item): void
public function add(ProjectionItemInterface $item): void
{
$cacheItem = $this->createCacheItem($item);

Expand All @@ -32,7 +33,7 @@ public function add(ProjectionItem $item): void
}
}

public function addDeferred(ProjectionItem $item): void
public function addDeferred(ProjectionItemInterface $item): void
{
$cacheItem = $this->createCacheItem($item);

Expand All @@ -48,7 +49,7 @@ public function flush(): void
}
}

public function get(ProjectionItem $item): ?ProjectionItem
public function get(ProjectionItemInterface $item): ?ProjectionItemInterface
{
$cacheItem = $this->cachePool->getItem($item->getKey());

Expand All @@ -59,7 +60,7 @@ public function get(ProjectionItem $item): ?ProjectionItem
return $this->serializer->deserialize($cacheItem->get(), get_class($item), self::JMS_SERIALIZER_FORMAT);
}

public function delete(ProjectionItem $item): void
public function delete(ProjectionItemInterface $item): void
{
if (!$this->cachePool->deleteItem($item->getKey())) {
throw new ProjectionException('Not possible to delete projection item on cache pool');
Expand All @@ -73,7 +74,7 @@ public function deleteByTags(Tags $tags): void
}
}

private function createCacheItem(ProjectionItem $item): CacheItemInterface
private function createCacheItem(ProjectionItemInterface $item): CacheItemInterface
{
$cacheItem = $this->cachePool->getItem($item->getKey());
$cacheItem->set($this->serializer->serialize($item, self::JMS_SERIALIZER_FORMAT));
Expand Down
20 changes: 11 additions & 9 deletions src/Tag/ProjectionTagGenerator.php
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections\Tag;

trait ProjectionTagGenerator
{
private static function createTagsFromArray(string ...$tagsAsStrings): Tags
private static function createTagsFromArray(string ...$tags): Tags
{
$tags = [];

foreach ($tagsAsStrings as $tag) {
$tags[] = new Tag($tag);
}

return new Tags(...$tags);
return new Tags(
...array_map(
function(string $tag): Tag {
return new Tag($tag);
},
$tags
)
);
}
}
3 changes: 2 additions & 1 deletion src/Tag/Tag.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);

namespace Kununu\Projections\Tag;

Expand Down
Loading

0 comments on commit 4fc1e44

Please sign in to comment.