Was this page helpful?

Caching in the PHP Delivery SDK

Content types, environment and space data change rarely, but you want to access them very often. They are a perfect candidate for caching: you save API calls and speed up execution. Let's take a look at how to locally cache their data using the PHP CDA SDK.

Delivery SDK and PSR-6

The SDK supports any PSR-6 cache pool. We suggest using either the Symfony Cache component for a comprehensive solution, or one of the packages in the PHP cache organization to solve a specific need; for our example we'll be using the latter, specifically the Filesystem adapter:

composer require cache/filesystem-adapter

After this step is done, you have two options for triggering a cache warmup: using the CLI utility bundled in the SDK, or manually handling the cache warmer for a better integration with your workflow. Let's take a look at both.

CLI utility

Because PSR-6 cache pools can wrap basically anything, the SDK can not know how to properly instantiate them; for this reason, the actual instantiation of the the cache pool must be handled by the user. The SDK will, in fact, expect the user to create a class which implements Contentful\Delivery\Cache\CacheItemPoolFactoryInterface:

namespace Contentful\Delivery\Cache;

/**
 * CacheItemPoolFactoryInterface.
 *
 * This interface must be implemented by a factory
 * in order to be compatible with CLI commands for warming up
 * and clearing the cache.
 */
interface CacheItemPoolFactoryInterface
{
    /**
     * Empty constructor.
     *
     * The factory should not expect to receive any parameters when being instantiated.
     */
    public function __construct();

    /**
     * Returns a PSR-6 CacheItemPoolInterface object.
     * The method receives two parameters, which can be used
     * for things like using a different directory in the filesystem,
     * but the implementation can simply not use them if they're not necessary.
     *
     * @param string $api           A string representation of the API in use,
     *                              it's the result of calling $client->getApi()
     * @param string $spaceId       The ID of the space
     * @param string $environmentId The ID of the environment
     *
     * @return CacheItemPoolInterface
     */
    public function getCacheItemPool($api, $spaceId, $environmentId);
}

Let's create a simple implementation:

namespace App\Cache;

use Cache\Adapter\Filesystem\FilesystemCachePool;
use Contentful\Delivery\Cache\CacheItemPoolFactoryInterface;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;

class AppCacheFactory implements CacheItemPoolFactoryInterface
{
    public function __construct()
    {
    }

    public function getCacheItemPool($api, $spaceId, $environmentId)
    {
        $path = \sprintf('%s/cache/%s-%s-%s/', __DIR__, $api, $spaceId, $environmentId);
        $filesystem = new Filesystem(new Local($path));

        return new FilesystemCachePool($filesystem);
    }
}

Now that we have everything we need, let's trigger a cache warmup:

php vendor/bin/contentful delivery:cache:warmup --access-token=$ACCESS_TOKEN --space-id=$SPACE_ID --environment-id=$ENVIRONMENT_ID --factory-class="App\\Cache\\AppCacheFactory"

A few notes about the command:

  • The environment ID parameter is optional, and will default to master.
  • --factory-class expects a fully-qualified class name (FQCN). The SDK uses the system autoloader, so anything in your project should work.
  • The command takes an optional --use-preview parameter, which will make the SDK use the Preview API instead of the Delivery API.
  • Use php vendor/bin/contentful help delivery:cache:warmup for a complete description.
  • A corresponding delivery:cache:clear command is provided, and it accepts the same parameters.

Manually calling the cache warmer

The CLI utility behind the scenes uses the class Contentful\Delivery\Cache\CacheWarmer. If you need to have a tighter integration in your code with cache warming/clearing, you can use yourself that class directly. The constructor expects two arguments: a Contentful\Delivery\Client object (configured without a cache setting) and a PSR-6 cache pool:

use App\Cache\AppCacheFactory;
use Contentful\Delivery\Cache\CacheWarmer;
use Contentful\Delivery\Client;

$client = new Client($accessToken, $spaceId, $environmentId);
// Here we use the class we defined above, but the important thing is to have
// a PSR-6 cache item pool object instantiated.
// Ideally, in a complete app, you would use some sort of dependency injection
// to get access to the pool
$cacheFactory = new AppCacheFactory();
$cacheItemPool = $cacheFactory->getCacheItemPool($client->getApi(), $spaceId, $environmentId);

$warmer = new CacheWarmer($client, $cacheItemPool);
if (!$wamer->warmUp()) {
  throw new \RuntimeException('Could not warm up the cache');
}

Configuring the client

Regardless how you configure the cache, you need to tell the client to use the cache pool. To do so, set the cache parameter to the PSR-6 cache pool in the options array of the client constructor:

use Contentful\Delivery\Client;

$client = new Client($accessToken, $spaceId, $environmentId, $usePreview = false, $defaultLocale = null, $options = [
    'cache' => $cacheItemPool,
]);

Now, the SDK will check the cache for space, environment, and content type information before querying the API. Bear in mind that when calling the collection endpoint for content types ($client->getContentTypes($query)) the cache can not be used, since the SDK can't reliably predict which content types will be returned.

Runtime, dynamic cache warm up

There is another option, which doesn't require you to statically cache data beforehand, and instead delegates that process to runtime when a new space, environment, or content type is found. This is useful when working with very dynamic applications or cache stores, or where data about Contentful (such as space ID or environment ID) is not known at build time.

To enable dynamic cache warm up, set the autoWarmup option to true in the client constructor:

use Contentful\Delivery\Client;

$client = new Client($accessToken, $spaceId, $environmentId, $usePreview = false, $defaultLocale = null, $options = [
    'cache' => $cacheItemPool,
    'autoWarmup' => true,
]);

When using this configuration, the SDK will automatically save in the configured PSR-6 cache pool any new instance of space, environment, and content types that is found. While this is a convenient and easy option, we still recommend statically warming up the cache instead, to avoid increased runtime complexity and possible performance hit (for instance if the cache is slow on write).

As a final note, remember one thing: should you make changes such as adding or removing a locale, or changing fields in a content type, you would need to regenerate the cache, as the SDK behavior is to use the cache whenever that's available, even at the cost of ignoring newer info. In the event of a new content type, its info would be picked up correctly — there would be nothing about it in the cache. Our suggestion is to warmup the cache at every change to the content types, so you can be sure to always have the best performance and never risk working with obsolete data. Should you make changes to a content type without purging its obsolete cache data, the SDK will still try to build the entry, but you may find yourself in a situation where some field is not built correctly, and is instead returned "raw", for instance a string rather than a Contentful\Core\Api\DateTimeImmutable object, or an array rather than a Contentful\Core\Api\Link object. For this reason, you should always try to do a cache purge on content type changes, either manually or using webhooks.