PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
204: HTTP Caching Headers in PHP
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
ETagand aCache-Controlheader setting the max-age to 600 seconds (10 minutes). - Check the
$_SERVER['HTTP_IF_NONE_MATCH']header; if it matches the current ETag, send a304 Not Modifiedresponse 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;
?>There are no comments for now.