Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

The Symfony Cache Component

The Symfony Cache Component

~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

The dashboard statistic from chapter 33 (ProjectStatsService) recomputes on EVERY page load, even though the underlying data often changes LESS OFTEN – time to cache the result.

CacheInterface: Symfony's unified cache abstraction

src/Service/ProjectStatsService.php
<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Project;
use App\Repository\TaskRepository;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

class ProjectStatsService
{
    public function __construct(
        private readonly TaskRepository $taskRepository,
        private readonly CacheInterface $cache,
    ) {
    }

    public function computeStats(Project $project): array
    {
        return $this->cache->get(
            sprintf('project_stats_%d', $project->getId()),
            function (ItemInterface $item) use ($project) {
                $item->expiresAfter(300); // 5 minutes

                return $this->computeStatsWithoutCache($project);
            }
        );
    }

    private function computeStatsWithoutCache(Project $project): array
    {
        // ... exactly the computation from chapter 33 ...
    }
}

CacheInterface is – EXACTLY like LoggerInterface or MailerInterface – an AUTOWIRED service (block 6). get() implements the "cache-aside" pattern in ONE call: if an entry for this key already exists, it gets returned DIRECTLY; if NONE exists (or it's expired), the callback gets EXECUTED, and the result gets STORED AND returned.

expiresAfter() vs. expiresAt()

$item->expiresAfter(300);                          // relative: 300 seconds from NOW
$item->expiresAt(new \DateTimeImmutable('tomorrow')); // absolute: a fixed point in time

Invalidating the cache on purpose

If a task changes (chapter 21), the cached statistics value gets STALE – instead of waiting for the 5-minute expiry, let's invalidate it DELIBERATELY:

// In TaskAssignmentService (chapter 36) or wherever tasks get changed:
$this->cache->delete(sprintf('project_stats_%d', $project->getId()));

Cache adapters: dev vs. prod

config/packages/cache.yaml
framework:
    cache:
        app: cache.adapter.filesystem  # default: filesystem-based, works everywhere
        # Better for production with several servers:
        # app: cache.adapter.redis
        # default_redis_provider: '%env(REDIS_URL)%'

The filesystem adapter works WITHOUT extra infrastructure, but is SERVER-BOUND – with MULTIPLE application servers (chapter 48), server A does NOT see server B's cache. Redis (a separate, shared cache service) solves this for multi-server deployments.

A preview of chapter 46: two different kinds of cache

Cache typeWhat it caches
This chapter: CacheInterface ("application cache")Caches ANY value WITHIN your PHP code – like our statistics computation.
Chapter 46: HTTP cacheCaches ENTIRE HTTP responses, often BEFORE your application, sometimes WITHOUT PHP even running.

Tipp: Rule of thumb for cache keys: ALWAYS include every value that affects the result (here: $project->getId()) – a TOO BROAD key (e.g. just 'project_stats' for ALL projects) would wrongly mix up data from DIFFERENT projects.