Skip to content

Commit

Permalink
Merge pull request #233 from contentful/feat/entries-warmup
Browse files Browse the repository at this point in the history
Add ability to cache entries and assets
  • Loading branch information
dborsatto authored Aug 24, 2018
2 parents 8d70ee9 + 0ca5dd3 commit df10a2f
Show file tree
Hide file tree
Showing 14 changed files with 1,386 additions and 102 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased](https://github.com/contentful/contentful.php/compare/3.3.0...HEAD)

### Added

* The SDK can now use a locally-cached copy of entries and asset. While this _will not_ prevent API calls when using `getEntries` or `getAssets`, it will intercept calls made with `getEntry` and `getAsset`, including those being made when resolving a link from an entry. You can enable this either using the CLI commands with the `--cache-content` flag, or passing `'cacheContent' => true` to the `$options` array in the client constructor (requires `'autoWarmup'` to also be set to true).

## [3.3.0](https://github.com/contentful/contentful.php/tree/3.3.0) (2018-06-18)

### Added
Expand Down
135 changes: 135 additions & 0 deletions src/Cache/BaseCacheHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

/**
* This file is part of the contentful/contentful package.
*
* @copyright 2015-2018 Contentful GmbH
* @license MIT
*/

namespace Contentful\Delivery\Cache;

use Contentful\Core\Resource\ResourceInterface;
use Contentful\Delivery\Client;
use Contentful\Delivery\InstanceRepository;
use Contentful\Delivery\Query;
use Contentful\Delivery\Resource\Locale;
use Psr\Cache\CacheItemPoolInterface;

abstract class BaseCacheHandler
{
/**
* @var Client
*/
protected $client;

/**
* @var CacheItemPoolInterface
*/
protected $cacheItemPool;

/**
* @var \Closure
*/
protected $toggler;

/**
* CacheWarmer constructor.
*
* @param Client $client
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct(Client $client, CacheItemPoolInterface $cacheItemPool)
{
$this->client = $client;
$this->cacheItemPool = $cacheItemPool;

$this->toggler = \Closure::bind(function (InstanceRepository $instanceRepository, $value) {
$previous = $instanceRepository->autoWarmup;
$instanceRepository->autoWarmup = (bool) $value;

return $previous;
}, null, InstanceRepository::class);
}

/**
* @param InstanceRepository $instanceRepository
* @param bool $value
*
* @return bool
*/
protected function toggleAutoWarmup(InstanceRepository $instanceRepository, $value)
{
$toggler = $this->toggler;

return $toggler($instanceRepository, $value);
}

/**
* @param bool $cacheContent
*
* @return ResourceInterface[]
*/
protected function fetchResources($cacheContent = false)
{
$resources = [
$this->client->getSpace(),
$this->client->getEnvironment(),
];

$query = (new Query())
->setLimit(100)
;
foreach ($this->client->getContentTypes($query) as $contentType) {
$resources[] = $contentType;
}

foreach ($resources as $resource) {
yield $resource;
}

if ($cacheContent) {
$locales = \array_map(function (Locale $locale) {
return $locale->getCode();
}, $this->client->getEnvironment()->getLocales());
$locales[] = '*';

foreach ($this->fetchCollection('Entry', $locales) as $entry) {
yield $entry;
}

foreach ($this->fetchCollection('Asset', $locales) as $asset) {
yield $asset;
}
}
}

/**
* @param string $type Either 'Entry' or 'Asset'
* @param string[] $locales
*
* @return \Generator
*/
private function fetchCollection($type, $locales)
{
foreach ($locales as $locale) {
$skip = 0;
do {
$query = (new Query())
->setLocale($locale)
->setLimit(1000)
->setSkip($skip)
;
$resources = 'Entry' === $type
? $this->client->getEntries($query)
: $this->client->getAssets($query);

foreach ($resources as $resource) {
yield $resource;
}

$skip += 1000;
} while ($resources->getTotal() > $resources->getSkip() + 1000);
}
}
}
48 changes: 9 additions & 39 deletions src/Cache/CacheClearer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,31 @@

namespace Contentful\Delivery\Cache;

use Contentful\Delivery\Client;
use Contentful\Delivery\Query;
use Psr\Cache\CacheItemPoolInterface;
use Contentful\Delivery\SystemProperties;

/**
* CacheClearer class.
*
* Use this class to clear the needed cache information from a
* PSR-6 compatible pool.
*/
class CacheClearer
class CacheClearer extends BaseCacheHandler
{
/**
* @var Client
*/
private $client;

/**
* @var CacheItemPoolInterface
*/
private $cacheItemPool;

/**
* CacheClearer constructor.
* @param bool $cacheContent
*
* @param Client $client
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct(Client $client, CacheItemPoolInterface $cacheItemPool)
{
$this->client = $client;
$this->cacheItemPool = $cacheItemPool;
}

/**
* @return bool
*/
public function clear()
public function clear($cacheContent = false)
{
$api = $this->client->getApi();
$space = $this->client->getSpace();
$environment = $this->client->getEnvironment();
$instanceRepository = $this->client->getInstanceRepository();

$keys = [
$instanceRepository->generateCacheKey($api, 'Space', $space->getId()),
$instanceRepository->generateCacheKey($api, 'Environment', $environment->getId()),
];

$query = (new Query())
->setLimit(100);
$contentTypes = $this->client->getContentTypes($query);

foreach ($contentTypes as $contentType) {
$keys[] = $instanceRepository->generateCacheKey($api, 'ContentType', $contentType->getId());
$keys = [];
foreach ($this->fetchResources($cacheContent) as $resource) {
/** @var SystemProperties $sys */
$sys = $resource->getSystemProperties();
$keys[] = $instanceRepository->generateCacheKey($api, $sys->getType(), $sys->getId(), $sys->getLocale());
}

return $this->cacheItemPool->deleteItems($keys);
Expand Down
61 changes: 14 additions & 47 deletions src/Cache/CacheWarmer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,74 +9,41 @@

namespace Contentful\Delivery\Cache;

use Contentful\Delivery\Client;
use Contentful\Delivery\Query;
use Psr\Cache\CacheItemPoolInterface;
use Contentful\Delivery\SystemProperties;
use function GuzzleHttp\json_encode as guzzle_json_encode;

/**
* CacheWarmer class.
*
* Use this class to save the needed cache information in a
* PSR-6 compatible pool.
*/
class CacheWarmer
class CacheWarmer extends BaseCacheHandler
{
/**
* @var Client
*/
private $client;

/**
* @var CacheItemPoolInterface
*/
private $cacheItemPool;

/**
* CacheWarmer constructor.
* @param bool $cacheContent
*
* @param Client $client
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct(Client $client, CacheItemPoolInterface $cacheItemPool)
{
$this->client = $client;
$this->cacheItemPool = $cacheItemPool;
}

/**
* @return bool
*/
public function warmUp()
public function warmUp($cacheContent = false)
{
$api = $this->client->getApi();
$instanceRepository = $this->client->getInstanceRepository();
$previous = $this->toggleAutoWarmup($instanceRepository, false);

$space = $this->client->getSpace();
$item = $this->cacheItemPool->getItem(
$instanceRepository->generateCacheKey($api, 'Space', $space->getId())
);
$item->set(\json_encode($space));
$this->cacheItemPool->saveDeferred($item);

$environment = $this->client->getEnvironment();
$item = $this->cacheItemPool->getItem(
$instanceRepository->generateCacheKey($api, 'Environment', $environment->getId())
);
$item->set(\json_encode($environment));
$this->cacheItemPool->saveDeferred($item);
foreach ($this->fetchResources($cacheContent) as $resource) {
/** @var SystemProperties $sys */
$sys = $resource->getSystemProperties();
$key = $instanceRepository->generateCacheKey($api, $sys->getType(), $sys->getId(), $sys->getLocale());

$query = (new Query())
->setLimit(100);
$contentTypes = $this->client->getContentTypes($query);
$item = $this->cacheItemPool->getItem($key);
$item->set(guzzle_json_encode($resource));

foreach ($contentTypes as $contentType) {
$item = $this->cacheItemPool->getItem(
$instanceRepository->generateCacheKey($api, 'ContentType', $contentType->getId())
);
$item->set(\json_encode($contentType));
$this->cacheItemPool->saveDeferred($item);
}

$this->toggleAutoWarmup($instanceRepository, $previous);

return $this->cacheItemPool->commit();
}
}
15 changes: 9 additions & 6 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ class Client extends BaseClient
* string, e.g. "en-US" to fetch content in that locale. Set it to "*"
* to fetch content in all locales.
* @param array $options An array of optional configuration. The following options are available:
* * guzzle Override the guzzle instance used by the Contentful client
* * logger A PSR-3 logger
* * baseUri Override the uri that is used to connect to the Contentful API (e.g. 'https://cdn.contentful.com/').
* * cache Null or a PSR-6 cache item pool. The client only writes to the cache if autoWarmup is true, otherwise, you are responsible for warming it up using \Contentful\Delivery\Cache\CacheWarmer.
* * autoWarmup Warm up the cache automatically
* * guzzle Override the guzzle instance used by the Contentful client
* * logger A PSR-3 logger
* * baseUri Override the uri that is used to connect to the Contentful API (e.g. 'https://cdn.contentful.com/').
* * cache Null or a PSR-6 cache item pool. The client only writes to the cache if autoWarmup is true, otherwise, you are responsible for warming it up using \Contentful\Delivery\Cache\CacheWarmer.
* * autoWarmup Warm up the cache automatically for content types and locales
* * cacheContent Warm up the cache automatically for entries and assets (requires autoWarmup to also be set to true)
*/
public function __construct($token, $spaceId, $environmentId = 'master', $preview = false, $defaultLocale = null, array $options = [])
{
Expand All @@ -122,6 +123,7 @@ public function __construct($token, $spaceId, $environmentId = 'master', $previe
'baseUri' => null,
'cache' => null,
'autoWarmup' => false,
'cacheContent' => false,
], $options);

$baseUri = $preview ? self::URI_PREVIEW : self::URI_DELIVERY;
Expand Down Expand Up @@ -150,7 +152,8 @@ public function __construct($token, $spaceId, $environmentId = 'master', $previe
$cacheItemPool,
(bool) $options['autoWarmup'],
$this->spaceId,
$this->environmentId
$this->environmentId,
(bool) $options['cacheContent']
);
$this->builder = new ResourceBuilder($this, $this->instanceRepository);
$this->scopedJsonDecoder = new ScopedJsonDecoder($this->spaceId, $this->environmentId);
Expand Down
8 changes: 5 additions & 3 deletions src/Console/ClearCacheCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ protected function configure()
'The FQCN of a factory class which implements "%s".',
CacheItemPoolFactoryInterface::class
)),
new InputOption('use-preview', 'p', InputOption::VALUE_NONE),
new InputOption('use-preview', 'p', InputOption::VALUE_NONE, 'Use the Preview API instead of the Delivery API'),
new InputOption('cache-content', 'c', InputOption::VALUE_NONE, 'Include entries and assets'),
]);
}

Expand All @@ -41,7 +42,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
$accessToken = $input->getOption('access-token');
$spaceId = $input->getOption('space-id');
$environmentId = $input->getOption('environment-id');
$usePreview = $input->getOption('use-preview');
$usePreview = (bool) $input->getOption('use-preview');
$cacheContent = (bool) $input->getOption('cache-content');

$client = new Client($accessToken, $spaceId, $environmentId, $usePreview);
$api = $client->getApi();
Expand All @@ -65,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

$clearer = new CacheClearer($client, $cacheItemPool);
if (!$clearer->clear()) {
if (!$clearer->clear($cacheContent)) {
throw new \RuntimeException(\sprintf(
'The SDK could not clear the cache. Try checking your PSR-6 implementation (class "%s").',
\get_class($cacheItemPool)
Expand Down
Loading

0 comments on commit df10a2f

Please sign in to comment.