Symfony PhpFilesAdapter - speed, simplicity, security
Up to to day PhpFilesAdapter stores compiled PHP files that OPcache reads fast. No services. Atomic writes. Good default for single-host pages and API fragments.
Install:
composer require symfony/cacheUsage Example:
<?php use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; require __DIR__ . '/vendor/autoload.php'; $cacheDir = __DIR__ . '/../var/cache'; // place outside web root $cache = new PhpFilesAdapter('myapp', 0, $cacheDir); $key = 'page:home:v1'; $html = $cache->get($key, function (ItemInterface $item) { $item->expiresAfter(900); // 15 minutes return render_home(); }); echo $html;

Resume:
- Files only, very fast with OPcache.
- Atomic writes via callback; simple stampede control.
- Per-host cache. Clear by versioning keys or deleting files.
Seamless injection patterns
1) Drop-in remember() helper
Wrap heavy calls without changing their internals. One line at the call site.
<?php use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; $cache = new PhpFilesAdapter('myapp', 0, __DIR__ . '/../var/cache'); function remember(PhpFilesAdapter $cache, string $key, int $ttl, callable $fn) { return $cache->get($key, function (ItemInterface $item) use ($ttl, $fn) { $item->expiresAfter($ttl); return $fn(); }); } $users = remember($cache, 'users:list:v1', 600, function () { return fetch_all_users(); });
2) Repository decorator
Keep your interface. Add caching by composing a decorator around the real repository.
<?php interface UserRepository { public function findById(int $id): array; } final class DbUserRepository implements UserRepository { public function findById(int $id): array { return db_load_user($id); } } use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; final class CachedUserRepository implements UserRepository { private UserRepository $inner; private PhpFilesAdapter $cache; public function __construct(UserRepository $inner, PhpFilesAdapter $cache) { $this->inner = $inner; $this->cache = $cache; } public function findById(int $id): array { $key = 'user:' . $id . ':v1'; return $this->cache->get($key, function (ItemInterface $item) use ($id) { $item->expiresAfter(1800); return $this->inner->findById($id); }); } } $cache = new PhpFilesAdapter('myapp', 0, __DIR__ . '/../var/cache'); $repo = new CachedUserRepository(new DbUserRepository(), $cache); $user = $repo->findById(42);
3) Full page cache in front controller
Cache whole responses per route without touching controllers. Works well for anonymous pages.
<?php use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; $cache = new PhpFilesAdapter('myapp', 0, __DIR__ . '/../var/cache'); $key = 'page:' . sha1($_SERVER['REQUEST_URI']) . ':v1'; $html = $cache->get($key, function (ItemInterface $item) { $item->expiresAfter(300); ob_start(); dispatch_request(); return ob_get_clean(); }); echo $html;
4) Fragment cache in a view helper
Cache expensive partials like sidebars or widgets without changing templates that call them.
<?php use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; $cache = new PhpFilesAdapter('myapp', 0, __DIR__ . '/../var/cache'); function render_top_stories(PhpFilesAdapter $cache): string { return $cache->get('fragment:top_stories:v2', function (ItemInterface $item) { $item->expiresAfter(600); $stories = load_top_stories(); return render('partials/top_stories.php', ['stories' => $stories]); }); }

5) Transparent SQL result cache
Wrap read-heavy queries. Key is based on SQL and parameters so call sites stay the same.
<?php use Symfony\Component\Cache\Adapter\PhpFilesAdapter; use Symfony\Contracts\Cache\ItemInterface; $cache = new PhpFilesAdapter('myapp', 0, __DIR__ . '/../var/cache'); function db_all_cached(PhpFilesAdapter $cache, string $sql, array $params, int $ttl = 120): array { $key = 'sql:' . sha1($sql . '|' . json_encode($params)) . ':v1'; return $cache->get($key, function (ItemInterface $item) use ($sql, $params, $ttl) { $item->expiresAfter($ttl); return db_all($sql, $params); }); } // usage $rows = db_all_cached($cache, 'SELECT * FROM posts WHERE tag = ? LIMIT 10', ['php']);

