Skip to Content
Course content

204: HTTP Caching Headers in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've ever wondered why your browser sometimes shows an old version of a page even after you've pushed a fix to the server, you've encountered the double-edged sword of HTTP caching. When done right, it makes your app feel instantaneous. When done wrong, it's a nightmare to debug. Today, we're going to build a small API endpoint that returns a list of product categories—data that doesn't change often, making it the perfect candidate for caching.

Setting up our Category API

First, let's create a simple script. In a real app, this would hit a database, but for this example, we'll use a static array. I want this endpoint to be fast, so I'm focusing on reducing the number of times the server actually has to process the request.

<?php
$categories = ['Electronics', 'Books', 'Clothing', 'Home & Garden', 'Toys'];
$data = json_encode($categories);

header('Content-Type: application/json');
echo $data;
?>

Right now, every single time a user refreshes the page, PHP boots up, encodes that array, and sends it over the wire. It's wasteful.

Telling the browser to stop asking for the same data

The easiest way to fix this is with the Cache-Control header. I'll tell the browser that this response is "fresh" for one hour (3600 seconds). This is called expiration-based caching.

<?php
$categories = ['Electronics', 'Books', 'Clothing', 'Home & Garden', 'Toys'];
$data = json_encode($categories);

header('Content-Type: application/json');
header('Cache-Control: public, max-age=3600'); 
echo $data;
?>

Now, if you check the Network tab in your DevTools, you'll see that subsequent requests don't even hit the server; the browser just pulls the result from its own disk. It's incredibly fast, but it creates a problem: if I add a new category, the user won't see it for an hour.

Oops, the 'Headers already sent' trap

While I was testing this, I tried to add a quick var_dump at the top of the file to debug my array. I did something like this:

<?php
var_dump($categories); // Debugging line
header('Cache-Control: public, max-age=3600');
// ... rest of the code
?>

Immediately, PHP screamed at me: Warning: Cannot modify header information - headers already sent. I've seen this a thousand times, and I still do it occasionally. Remember, header() calls must happen before any actual output is sent to the browser—even a single blank space outside the <?php tag can trigger this. I'll strip out the debug code and move the headers to the very top.

Using ETags for smarter validation

Expiration is great, but what if we want the browser to check if the data has changed without downloading the whole payload again? That's where ETags (Entity Tags) come in. An ETag is basically a fingerprint of the content.

I'll create a hash of the data and send it. Then, the next time the browser asks for the page, it will send that hash back in an If-None-Match header. If the hash is still the same, we tell the browser "304 Not Modified" and send absolutely no body content.

<?php
$categories = ['Electronics', 'Books', 'Clothing', 'Home & Garden', 'Toys'];
$data = json_encode($categories);

// Create a unique fingerprint based on the content
$etag = md5($data);

header('Content-Type: application/json');
header('ETag: "' . $etag . '"');
header('Cache-Control: public, max-age=3600');

// Check if the browser already has this version
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $etag) {
    header('HTTP/1.1 304 Not Modified');
    exit;
}

echo $data;
?>

This is the "gold standard" for API caching. We still have the hour-long expiration, but we've added a validation layer. If the browser's cache expires, it asks the server, "I have version XYZ, is it still good?" and the server can say "Yes" without wasting bandwidth sending the same JSON list again.




📋 Practical Task

Implementing Conditional Caching for a User Profile API

You are building a profile endpoint profile.php that returns a user's public information (name, bio, and join date) in JSON format. This data changes infrequently.

Your task: Modify the provided script to implement ETag-based caching. The script should:

  • Generate an MD5 hash of the JSON data to use as an ETag.
  • Send the ETag and a Cache-Control header setting the max-age to 600 seconds (10 minutes).
  • Check the $_SERVER['HTTP_IF_NONE_MATCH'] header; if it matches the current ETag, send a 304 Not Modified response and stop execution immediately.
  • Ensure no output is sent before the headers are declared.
<?php
// Mock user data
$user = [
    'name' => 'Jane Doe',
    'bio' => 'Software Architect and PHP enthusiast',
    'joined' => '2022-05-15'
];

$json_data = json_encode($user);

// TODO: Implement ETag and Cache-Control logic here

header('Content-Type: application/json');
echo $json_data;
?>
Rating
0 0

There are no comments for now.

to be the first to leave a comment.