Proxy Client Setup

This library ships with clients for the Varnish and NGINX caching servers and the Symfony built-in HTTP cache.

A Multiplexer client that forwards calls to multiple proxy clients is available, mainly for transition scenarios of your applications. A Noop client that implements the interfaces but does nothing at all is provided for local development and testing purposes.

The recommended usage is to have your application interact with the cache invalidator which you set up with the proxy client suitable for the proxy server you use.

Supported invalidation methods

Not all clients support all invalidation methods. This table provides of methods supported by each proxy client:

Client

Purge

Refresh

Ban

Tagging

Clear

Varnish

Fastly

NGINX

Symfony Cache

✓ (1)

✓ (1)

Cloudflare

✓ (2)

Noop

Multiplexer

(1): Only when using Toflar Psr6Store.
(2): Only available with Cloudflare Enterprise.

If needed, you can also implement your own client for other needs. Have a look at the interfaces in namespace FOS\HttpCache\ProxyClient\Invalidation.

“Clear” can be emulated by “Ban” with a request that matches everything. If both are available, “Clear” is preferred as it can be implemented by the caching proxy more efficiently.

Setup

Most proxy clients use the HttpDispatcher to send requests to the proxy server. The HttpDispatcher is built on top of the HTTPlug abstraction to be independent of specific HTTP client implementations.

Basic HTTP setup with HttpDispatcher

The dispatcher needs to know the IP addresses or hostnames of your proxy servers. If your proxy servers do not run on the default port (80 for HTTP, 443 for HTTPS), you need to specify the port with the server name. Make sure to provide the direct access to the web server without any other proxies that might block invalidation requests.

The server IPs are sufficient for invalidating absolute URLs. If you want to use relative paths in invalidation requests, supply the hostname and possibly a base path to your website with the $baseUri parameter:

use FOS\HttpCache\ProxyClient\HttpDispatcher;

$servers = ['10.0.0.1', '10.0.0.2:6081']; // Port 80 assumed for 10.0.0.1
$baseUri = 'my-cool-app.com';
$httpDispatcher = new HttpDispatcher($servers, $baseUri);

If your web application is accessed on a port other than the default port, make sure to include that port in the base URL:

$baseUri = 'my-cool-app.com:8080';

You can additionally specify the HTTP client and URI factory that should be used. If you specify a custom HTTP client, you need to configure the client to convert HTTP error status into exceptions. This can either be done in a client specific way or with the HTTPlug PluginClient and the ErrorPlugin. If client and/or URI factory are not specified, the dispatcher uses HTTPlug discovery to find available implementations.

Learn more about available HTTP clients in the HTTPlug documentation. To customize the behavior of the HTTP client, you can use HTTPlug plugins.

Varnish Client

The Varnish client sends HTTP requests with the HttpDispatcher. Create the dispatcher as explained above and pass it to the Varnish client:

use FOS\HttpCache\ProxyClient\Varnish;

$varnish = new Varnish($httpDispatcher);

Note

To make invalidation work, you need to configure Varnish accordingly.

You can also pass some options to the Varnish client:

  • tags_header (default: X-Cache-Tags): The HTTP header used to specify which tags to invalidate when sending invalidation requests to the caching proxy. Make sure that your Varnish configuration corresponds to the header used here;

  • tag_mode (default: ban): Select whether to invalidate tags using the xkey module or with ban requests. Supported modes: ban and purgekeys.

  • header_length (default: 7500): Control the maximum header length when invalidating tags. If there are more tags to invalidate than fit into the header, the invalidation request is split into several requests;

  • default_ban_headers (default: []): Map of headers that are set on each ban request, merged with the built-in headers.

Additionally, you can specify the request factory used to build the invalidation HTTP requests. If not specified, auto discovery is used - which usually is fine.

A full example could look like this:

$options = [
    'tags_header' => 'X-Custom-Tags-Header',
    'header_length' => 4000,
    'default_ban_headers' => [
        'EXTRA-HEADER' => 'header-value',
    ]
];
$requestFactory = new MyRequestFactory();

$varnish = new Varnish($httpDispatcher, $options, $requestFactory);

Configuring the Client for xkey Tag Invalidation

If you install the varnish modules to use xkey tagging, you need to adjust the Varnish client as well:

use FOS\HttpCache\ProxyClient\Varnish;

$options = [
    'tag_mode' => 'purgekeys'
];

$varnish = new Varnish($httpDispatcher, $options);

If you do not want to use soft purge (either because your varnish modules version is too old to support it or because it does not fit your scenario), additionally set the tags_header option to xkey-purge instead of the default xkey-softpurge.

Note

For xkey to work, the response tags MUST be given in a header named xkey and separated by space rather than the default ,. If you use the ResponseTagger, set it up with a custom TagHeaderFormatter.

Fastly Client

The Fastly client sends HTTP requests with the HttpDispatcher. Create the dispatcher as explained above and pass it to the Fastly client:

use FOS\HttpCache\ProxyClient\Fastly;

$fastly = new Fastly($httpDispatcher);

Note

Unlike other supported proxies there is no configuration needed for the proxy itself as all invalidation is done against Fastly Purge API. But for optimal use make sure to tune configuration together with Fastly.

You need to pass the following options to the Fastly client:

  • service_identifier: Identifier for your Fastly service account.

  • authentication_token: User token for authentication against Fastly APIs.

  • NB: To be able to clear all cache(->clear()), you’ll need a token for user with Fastly “Engineer permissions”.

  • soft_purge (default: true): Boolean for doing soft purges or not on tag & URL purging. Soft purges expires the cache unlike hard purge (removal), and allow grace/stale handling within Fastly VCL.

Additionally, you can specify the request factory used to build the invalidation HTTP requests. If not specified, auto discovery is used - which usually is fine.

A full example could look like this:

$options = [
    'service_identifier' => '<my-app-identifier>',
    'authentication_token' => '<user-authentication-token>',
    'soft_purge' => false
];
$requestFactory = new MyRequestFactory();

$fastly = new Fastly($httpDispatcher, $options, $requestFactory);

NGINX Client

The NGINX client sends HTTP requests with the HttpDispatcher. Create the dispatcher as explained above and pass it to the NGINX client:

use FOS\HttpCache\ProxyClient\Nginx;

$nginx = new Nginx($httpDispatcher);

If you have configured NGINX to support purge requests at a separate location, call setPurgeLocation():

use FOS\HttpCache\ProxyClient\Nginx;

$nginx = new Nginx($servers, $baseUri);
$nginx->setPurgeLocation('/purge');

Note

To use the client, you need to configure NGINX accordingly.

Symfony Client

The Symfony client sends HTTP requests with the HttpDispatcher. Create the dispatcher as explained above and pass it to the Symfony client:

use FOS\HttpCache\ProxyClient\Symfony;

$symfony = new Symfony($httpDispatcher);

Note

To make invalidation work, you need to use the EventDispatchingHttpCache.

KernelDispatcher for Single Server Installations

The HttpDispatcher sends real HTTP requests using any instance of HttpAsyncClient available in your application. If your application runs on one single server, you can call the cache kernel directly, inside the same PHP process, instead of sending actual HTTP requests over the network. This makes your setup easier as you don’t need to know the IP of your server and will also save server resources.

To do this, use the KernelDispatcher instead of the HttpDispatcher. This alternate dispatcher expects a HttpCacheProvider in the constructor to provide the HttpCache. The cache is implemented with the decorator pattern and thus the application kernel does not normally know about the cache. This library provides the HttpCacheAware trait to simplify making your kernel capable of providing the cache.

The recommended way to wire things up is to instantiate the cache kernel in the kernel constructor to guarantee consistent setup over all entry points. Adjust your kernel like this:

// src/AppKernel.php

namespace App;

use FOS\HttpCache\SymfonyCache\HttpCacheAware;
use FOS\HttpCache\SymfonyCache\HttpCacheProvider;
use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel implements HttpCacheProvider
{
    use HttpCacheAware;
    //...

    public function __construct(...)
    {
        // ...
        $this->setHttpCache(new AppCache($this));
    }
}

And adapt your bootstrapping code to use the cache kernel:

// public/index.php

use FOS\HttpCache\ProxyClient\Symfony;
use FOS\HttpCache\SymfonyCache\KernelDispatcher;

$kernel = new App\AppKernel();
$cacheKernel = $kernel->getHttpCache();

// Create the Symfony proxy client with KernelDispatcher
// Use $kernel, not $cacheKernel here!
$kernelDispatcher = new KernelDispatcher($kernel);
$symfony = new Symfony($kernelDispatcher);

...
$response = $cacheKernel->handle($request);
...

Cloudflare Client

Note

Cloudflare does not cache HTML pages by default. To cache them, you need to enable custom caching with page rules in the Cloudflare administration interface.

The Cloudflare client does invalidation requests with the Cloudflare Purge API.

The Cloudflare client sends HTTP requests with the HttpDispatcher. Create the dispatcher as explained above. Set the server list to the Cloudflare API [‘https://api.cloudflare.com’]. Do not specify a base URI. The Cloudflare client does not work with base URIs, you need to always specify the full URL including domain name.

Then create the Cloudflare client with the dispatcher. You also need to pass the following options:

  • authentication_token: User API token for authentication against Cloudflare APIs, requires Zone.Cache Purge permissions.

  • zone_identifier: Identifier for the Cloudflare zone you want to purge the cache for (see below how to obtain the identifier for your domain).

A full example could look like this:

use FOS\HttpCache\CacheInvalidator;
use FOS\HttpCache\ProxyClient\Cloudflare;
use FOS\HttpCache\ProxyClient\HttpDispatcher;

$options = [
    'authentication_token' => '<user-authentication-token>',
    'zone_identifier' => '<my-zone-identifier>',
];

$httpDispatcher = new HttpDispatcher(['https://api.cloudflare.com']);
$cloudflare = new Cloudflare($httpDispatcher, $options);
$cacheInvalidator = new CacheInvalidator($cloudflare);

When purging the cache by URL, see the Cloudflare Purge by URL docs for information about how Cloudflare purges by URL and what headers you can pass to a invalidatePath() request to clear the cache correctly.

You need to always specify the domain to invalidate (the base URI mechanism of the HttpDispatcher is not available for Cloudflare):

$cacheInvalidator->invalidatePath('https://example.com/path')->flush();

Note

Cloudflare supports different cache purge methods depending on your account. All Cloudflare accounts support purging the cache by URL and clearing all cache items. You need a Cloudflare Enterprise account to purge by cache tags.

Zone identifier

To find the zone identifier for your domain request this from the API:

curl -X GET "https://api.cloudflare.com/client/v4/zones?name={DOMAIN.COM}" \
-H "Authorization: Bearer {API TOKEN}" \
-H "Content-Type:application/json"

The zone identifier is returned in the id field of the results and is a 32-character hexadecimal string.

Noop Client

The Noop (no operation) client implements the interfaces for invalidation, but does nothing. It is useful for developing your application or on a testing environment that does not have a proxy server set up. Rather than making the cache invalidator optional in your code, you can (based on the environment) determine whether to inject the real client or the Noop client. The rest of your application then does not need to worry about the environment.

Multiplexer Client

The MultiplexerClient allows to send invalidation requests to multiple proxy clients.

It is useful when multiple caches exist in the environment and they need to be handled at the same time; the Multiplexer proxy client will forward the cache invalidation calls to all proxy clients supporting the operation in question:

use FOS\HttpCache\ProxyClient\MultiplexerClient;
use FOS\HttpCache\ProxyClient\Nginx;
use FOS\HttpCache\ProxyClient\Symfony;

$nginxClient = new Nginx($servers);
$symfonyClient = new Symfony([...]);
// Expects an array of ProxyClient in the constructor
$client = new MultiplexerClient([$nginxClient, $symfonyClient]);

Invalidation calls on MultiplexerClient will be forwarded to all proxy clients that support the invalidation method and be ignored if none do. Calling getTagsHeaderValue and getTagsHeaderName will throw an UnsupportedProxyOperationException if none of the proxy clients support tagging (i.e., implement TagCapable).

Note

Having multiple layers of HTTP caches in place is not a good idea in general. The MultiplexerClient is provided for special situations, for example during a transition phase of an application where an old and a new system run in parallel.

Note

When using the multiplexer, code relying on instanceof checks on the client and also the CacheInvalidator::supports method will not work, as the MultiplexerClient implements all interfaces, but the attached clients might not. Make sure that none of the code you use relies on such checks - or write your own multiplexer that only implements the interfaces supported by the clients you use.

Using the Proxy Client

The recommended usage of the proxy client is to create an instance of CacheInvalidator with the correct client for your setup. See The Cache Invalidator for more information.

Implementation Notes

Each client is an implementation of ProxyClient. All other interfaces, PurgeCapable, RefreshCapable, BanCapable, TagCapable and ClearCapable extend this ProxyClient. So each client implements at least one of the invalidation methods depending on the proxy server’s abilities. To interact with a proxy client directly, refer to the documentation comments on the interfaces.

The ProxyClient has one method: flush(). After collecting invalidation requests, flush() needs to be called to actually send the requests to the proxy server. This is on purpose: this way, we can send all requests together, reducing the performance impact of sending invalidation requests.