Performance
Every chapter so far has focused on correctness, structure, and security. This chapter covers the other axis: making an application fast enough — and, more importantly, knowing exactly where it's slow before changing anything, rather than guessing.
OPcache — Caching Compiled PHP, Not Application Data
Every time a plain PHP script runs, PHP first compiles the source code into "opcodes" before executing them — a real cost, repeated on every single request, for files that haven't changed at all between requests.
Profiling — Finding Out Where Time Actually Goes
Guessing at performance problems is genuinely unreliable — the code that "feels slow" while reading it is frequently not where the real time is spent. A profiler measures actual execution time per function call, across a real run of the application.
microtime(true) before and after gives a quick, genuinely useful measurement without installing any tooling — a reasonable first step before reaching for a full profiler.
The N+1 Query Problem — A Classic Performance Bug
"N+1" refers to the pattern: 1 initial query, then N more queries (one per row) inside a loop — for 100 posts, that's 101 total database round-trips instead of 1. This is one of the single most common real-world PHP performance problems, and it's a structural fix (combining queries), not a "make PHP faster" fix.
Application-Level Caching
A simple file-based cache here; real applications typically use Redis or Memcached for this purpose instead. The core idea is identical regardless of storage: run an expensive operation once, store the result, and serve the stored result to subsequent requests until it's considered stale.
| Layer | Caches | Example |
|---|---|---|
| OPcache | Compiled PHP opcodes | Automatic, set in php.ini |
| Application cache | Expensive computed results (queries, API responses) | Redis, Memcached, file-based |
| HTTP cache headers | Whole responses, at the browser/CDN level | Cache-Control, ETag headers |
Coding Challenges
Explain in your own words the difference between what OPcache caches and what an application-level cache (like Redis) caches, using a concrete example of each kind of content being cached.
📄 View solutionIdentify the N+1 query problem in this code, and rewrite it using a single JOIN query instead: $comments = $pdo->query("SELECT * FROM comments")->fetchAll(); foreach ($comments as $c) { $stmt = $pdo->prepare("SELECT name FROM users WHERE id = :id"); $stmt->execute(['id' => $c['user_id']]); $author = $stmt->fetch(); }
Write a function getCachedOrCompute(string $cacheFile, int $ttlSeconds, callable $computeFn) that generalises the chapter's file-cache example — if a fresh cache file exists, return its decoded contents; otherwise call $computeFn(), cache its result, and return it. Show it being used to cache the result of an expensive function getExpensiveReport().
Chapter 9 Quick Reference
- OPcache — caches COMPILED PHP code (opcodes), not application data; configured in php.ini
- Profiling — measure before optimising; microtime(true) for quick checks, Xdebug for full profiles
- N+1 query problem — one query, then one MORE per row in a loop; fix with a JOIN
- Application-level caching — store an expensive result once, serve it until stale (Redis, Memcached, file-based)
- Cache invalidation — often the hardest part: deciding exactly when cached data becomes stale
- Three caching layers: OPcache (code), application cache (data), HTTP cache headers (whole responses)
- Next chapter: capstone — a small REST API with auth, database, and tests