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:


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

Tags: