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/cache

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