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:
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.
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.
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.
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.
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.
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']);