8. Cache
BlockBundle
integrates the CacheBundle to handle block cache. The cache unit stored in the backend is a Response object from the HttpFoundation
Component.
Why a Response object ? It is a simple element, which contains the data (the body) and some metadata (the headers).
As the block returns a Response object, it is possible to send it to the client, this use case can be quite useful for some cache backends (esi, ssi or js).
8.1. Cache Behavior
The BlockBundle
assumes that a block can be cached by default, so if a cache backend is configured, the block response will be cached.
The default TTL is 86400 seconds. Now, there are many ways to control this behavior:
set a
TTL
setting inside the block, so ifttl
equals 0, then no cache will be available for the block and its parents,set a
use_cache
setting tofalse
ortrue
, if the variable is set tofalse
then no cache will be available for the block and its parents,no cache backend by default! By default, there is no cache backend setup, you should focus on raw performance before adding cache layers.
If you are extending the BaseBlockService
, you can use the method renderPrivateResponse
to return a private Response:
// Private response as the block response depends on the connected user
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$user = false;
if ($this->securityContext->getToken()) {
$user = $this->securityContext->getToken()->getUser();
}
if (!$user instanceof UserInterface) {
$user = false;
}
return $this->renderPrivateResponse($blockContext->getTemplate(), [
'user' => $user,
'block' => $blockContext->getBlock(),
'context' => $blockContext,
]);
}
or you can use the Response object:
// Private response as the block response depends on the connected user
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$user = false;
if ($this->securityContext->getToken()) {
$user = $this->securityContext->getToken()->getUser();
}
if (!$user instanceof UserInterface) {
$user = false;
}
return new Response(sprintf("your name is %s", $user->getUsername()))->setTtl(0)->setPrivate();
}
8.2. Cache Keys
The SonataCacheBundle
needs cache keys in order to find cached blocks.
By default cache keys consist of the block_id
and the updated_at
timestamp. Because these values change on every call from the Twig Helper,
it is mandatory to overwrite the getCacheKeys
function in your custom block class:
namespace App\Block;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
final class CachedBlock extends AbstractBlockService
{
public function execute(BlockContextInterface $blockContext, Response $response = null): Response
{
// ...
}
public function getCacheKeys(BlockInterface $block): array
{
return [
'id' => 'sample_cached_block'
];
}
}
8.3. Block TTL computation
The BlockBundle
uses the TTL value from the Response object to compute the final TTL value. As blocks can have children, the smallest TTL need to be used in parent block responses.
This job is done by the BlockRenderer
class, this service stores a state with the last response and compares the TTL with the current response.
The state is reset when the block does not have any parent.
The cache mechanism will use the TTL to set a valid value when the response is stored into the cache backend.
Note
If a TTL is set into a block container, the TTL value is not applied to the final Response object sent to the client. This can be done by using a different mechanism.
8.4. Final Response TTL computation
The BlockRendered
stores a global state for the smallest TTL available, there is another service used to store the smallest
TTL for the page: HttpCacheHandler
. Why two services? This has been done to add an extra layer of control.
The HttpCacheHandler::updateMetadata
is called by the twig extension when the response is retrieved, then an event listener is registered to alter the final Response.
The service can be configured using the http_cache_handler
key.
# config/packages/sonata_block.yaml
sonata_block:
http_cache:
handler: sonata.block.cache.handler.noop # no cache alteration
handler: sonata.block.cache.handler.default # default value
listener: true # default to true, register or not the event listener to alter the final response
8.5. Cache Backends
sonata.cache.mongo
: use mongodb to store cache element. This is a nice backend as you can remove a cache element by only one value. (remove all block where profile.media.id == 3 is used.)sonata.cache.memcached
: use memcached as a backend, shared across multiple hostssonata.cache.apc
: use apc from PHP runtime, cannot be shared across multiple hosts, and it is not suitable to store high volume of datasonata.cache.esi
: use an ESI compatible backend to store the cache, like Varnishsonata.cache.ssi
: use an SSI compatible backend to store the cache, like Apache or Nginx
8.6. Cache configuration
The configuration is defined per block service, so if you want to use memcached for a block type sonata.page.block.container
then use the following configuration:
# config/packages/sonata_block.yaml
sonata_block:
blocks:
sonata.page.block.container:
cache: sonata.cache.memcached
Also, make sure the memcached backend is configured in the sonata_cache
definition:
# config/packages/sonata_cache.yaml
sonata_cache:
caches:
memcached:
prefix: test # prefix to ensure there is no clash between instances
servers:
- { host: 127.0.0.1, port: 11211, weight: 0 }