Introduction
Caching is the single most impactful performance optimization for any PHP website. When I built ViralVidVault, file-based page caching reduced our average page load time by over 70%. Here's exactly how to implement it.
The Architecture
The concept is simple: render a page once, save the HTML to a file, and serve that file directly for subsequent requests until it expires. No database queries, no template rendering, no PHP processing beyond reading a file.
Request → Check cache file exists & not expired?
├─ YES → Read file → Send response (fast!)
└─ NO → Render page → Save to cache → Send response
Implementation
The Cache Class
<?php
class PageCache
{
private string $cacheDir;
private array $ttlRules = [];
private int $defaultTtl;
public function __construct(string $cacheDir, int $defaultTtl = 10800)
{
$this->cacheDir = rtrim($cacheDir, '/');
$this->defaultTtl = $defaultTtl;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function setTTL(string $pattern, int $seconds): void
{
$this->ttlRules[$pattern] = $seconds;
}
public function serve(): bool
{
if ($_SERVER['REQUEST_METHOD'] !== 'GET') return false;
$file = $this->getCacheFile();
if (!file_exists($file)) return false;
$ttl = $this->getTTL();
$age = time() - filemtime($file);
if ($age > $ttl) {
unlink($file);
return false;
}
// Send cache headers
header('X-Cache: HIT');
header('X-Cache-Age: ' . $age);
header('Cache-Control: public, max-age=' . ($ttl - $age));
readfile($file);
return true;
}
public function store(string $html): void
{
$file = $this->getCacheFile();
$dir = dirname($file);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($file, $html, LOCK_EX);
}
public function clear(string $path = ''): void
{
if ($path) {
$file = $this->cacheDir . '/' . md5($path) . '.html';
if (file_exists($file)) unlink($file);
} else {
$files = glob($this->cacheDir . '/*.html');
array_map('unlink', $files ?: []);
}
}
private function getCacheFile(): string
{
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $this->cacheDir . '/' . md5($uri) . '.html';
}
private function getTTL(): int
{
$uri = $_SERVER['REQUEST_URI'] ?? '/';
foreach ($this->ttlRules as $pattern => $ttl) {
if ($pattern === $uri || fnmatch($pattern, $uri)) {
return $ttl;
}
}
return $this->defaultTtl;
}
}
Integration in index.php
Here's how it plugs into a typical PHP entry point:
<?php
// public/index.php
$cache = new PageCache(__DIR__ . '/../data/pagecache');
$cache->setTTL('/', 10800); // Home: 3h
$cache->setTTL('/category/*', 10800); // Categories: 3h
$cache->setTTL('/watch/*', 21600); // Watch: 6h
$cache->setTTL('/search*', 600); // Search: 10min
// Try cache first
if ($cache->serve()) {
exit;
}
// Cache miss - render normally
ob_start();
// ... routing, controller, template rendering ...
require __DIR__ . '/../app/bootstrap.php';
$html = ob_get_flush();
$cache->store($html);
Cache Invalidation
The hardest problem in computer science, right? In practice, for a content site, you invalidate in two places:
- After cron fetches new content — Clear home and category caches
- After admin actions — Clear specific page caches
// In your cron script, after fetching new videos:
$cache = new PageCache(__DIR__ . '/../data/pagecache');
$cache->clear('/'); // Home page
$cache->clear(); // Or clear everything after a big update
Performance Results
On ViralVidVault, adding this middle cache layer produced significant gains:
| Metric | Before | After |
|---|---|---|
| Avg page load | 340ms | 95ms |
| TTFB | 280ms | 45ms |
| DB queries/request | 8-12 | 0 (cache hit) |
| PHP memory | 14MB | 4MB |
Tips
- Use LOCK_EX when writing to prevent partial reads during concurrent writes
- Don't cache POST requests or pages with user-specific content
- Add X-Cache headers for easy debugging
- Run garbage collection periodically to clean up expired files
- Use md5 of URI as filename to handle any URL safely
This exact pattern powers the cache layer at viralvidvault.com. It sits between LiteSpeed's built-in cache and the application's data cache, creating a 3-tier system that keeps the site snappy for users browsing viral videos from across Europe.
Part of the "Building ViralVidVault" series.
Top comments (0)