73

I have a PHP 5.1.0 website (actually it's 5.2.9 but it must also run on 5.1.0+).

Pages are generated dynamically but many of them are mostly static. By static I mean the content don't change but the "template" around the content can change over time.

I know they are several cache systems and PHP frameworks already out there, but my host don't have APC or Memcached installed and I'm not using any framework for this particular project.

I want the pages to be cached (I think by default PHP "disallow" cache). So far I'm using:

session_cache_limiter('private'); //Aim at 'public' session_cache_expire(180); header("Content-type: $documentMimeType; charset=$documentCharset"); header('Vary: Accept'); header("Content-language: $currentLanguage"); 

I read many tutorials but I can't find something simple (I know cache is something complex, but I only need some basic stuff).

What are "must" have headers to send to help caching?

2
  • How to specify caching for .js and .css files? Or are they included in the headers generated by the PHP file? Commented Jan 6, 2020 at 2:12
  • @David Spector You would cache them with your webserver. Under Apache you could do this with an .htaccess look for "Header set Cache-Control". You might want to try to ask a question as comments are not at good place for that :) Commented Jan 6, 2020 at 3:36

7 Answers 7

55

You might want to use private_no_expire instead of private, but set a long expiration for content you know is not going to change and make sure you process if-modified-since and if-none-match requests similar to Emil's post.

$tsstring = gmdate('D, d M Y H:i:s ', $timestamp) . 'GMT'; $etag = $language . $timestamp; $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false; $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false; if ((($if_none_match && $if_none_match == $etag) || (!$if_none_match)) && ($if_modified_since && $if_modified_since == $tsstring)) { header('HTTP/1.1 304 Not Modified'); exit(); } else { header("Last-Modified: $tsstring"); header("ETag: \"{$etag}\""); } 

Where $etag could be a checksum based on the content or the user ID, language, and timestamp, e.g.

$etag = md5($language . $timestamp); 
Sign up to request clarification or add additional context in comments.

10 Comments

What you've described would be a Weak E-Tag and should have a "W/" prefix.
I would add "Expires" header as well, as some hosting providers (e.g. servers) send it anyway and they do with date way back in the past, while I think it should be in the future.
If you're not getting 304s with the above, check there aren't any stray quote marks in HTTP_IF_NONE_MATCH. Replace $if_none_match == $etag with rtrim(ltrim($if_none_match, "'\""), "'\"") == $etag to be sure.
... and not to forget that an ideal E-Tag is calculated based on the fact that you can read the page content. The HTML that is to be sent to the browser, BEFORE sending it to the browser that is. Generally it also implies use of buffers. Probably already managed by modern php frameworks. If you want to send a "please do not cache me" header, its better use a Cache-Control. Why Cache-Control and not Pragma? Because Pragma is deprecated. In consequence, its better to send a Cache-Control header. Points down to give misleading. Nice try, though :)
Where is the $timestamp coming from? Should that be defined before the rest of the code $timestamp = time(); instead? Am I missing something?
|
17

You must have an Expires header. Technically, there are other solutions, but the Expires header is really the best one out there, because it tells the browser to not recheck the page before the expiration date and time and just serve the content from the cache. It works really great!

It is also useful to check for a If-Modified-Since header in the request from the browser. This header is sent when the browser is "unsure" if the content in it's cache is still the right version. If your page is not modified since that time, just send back an HTTP 304 code (Not Modified). Here is an example that sends a 304 code for ten minutes:

<?php if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < time() - 600) { header('HTTP/1.1 304 Not Modified'); exit; } } ?> 

You can put this check early on in your code to save server resources.

Comments

15

This is the best solution for php cache Just use this in the top of the script

$seconds_to_cache = 3600; $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; header("Expires: $ts"); header("Pragma: cache"); header("Cache-Control: max-age=$seconds_to_cache"); 

3 Comments

It would really help to explain your answer so others know why this is the best solution!
Add this code to the top of your PHP page, it will force cache your file to the browser
This worked, but my nginx headers were overriding my PHP headers so I needed to add session_cache_limiter('public') right before my PHP headers to allow them to override my nginx headers. See stackoverflow.com/a/42947961/2017192
12

Take your pick - or use them all! :-)

 header('Expires: Thu, 01-Jan-70 00:00:01 GMT'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false); header('Pragma: no-cache'); 

2 Comments

session_cache_limiter and session_cache_expire already control Expires, Cache-Control, Last-Modified and Pragma...
I see. Yes, you are right. And I also see that I did not read your question well enough. sorry
9

Here's a small class that does http caching for you. It has a static function called 'Init' that needs 2 parameters, a timestamp of the date that the page (or any other file requested by the browser) was last modified and the maximum age, in seconds, that this page can be held in cache by the browser.

class HttpCache { public static function Init($lastModifiedTimestamp, $maxAge) { if (self::IsModifiedSince($lastModifiedTimestamp)) { self::SetLastModifiedHeader($lastModifiedTimestamp, $maxAge); } else { self::SetNotModifiedHeader($maxAge); } } private static function IsModifiedSince($lastModifiedTimestamp) { $allHeaders = getallheaders(); if (array_key_exists("If-Modified-Since", $allHeaders)) { $gmtSinceDate = $allHeaders["If-Modified-Since"]; $sinceTimestamp = strtotime($gmtSinceDate); // Can the browser get it from the cache? if ($sinceTimestamp != false && $lastModifiedTimestamp <= $sinceTimestamp) { return false; } } return true; } private static function SetNotModifiedHeader($maxAge) { // Set headers header("HTTP/1.1 304 Not Modified", true); header("Cache-Control: public, max-age=$maxAge", true); die(); } private static function SetLastModifiedHeader($lastModifiedTimestamp, $maxAge) { // Fetching the last modified time of the XML file $date = gmdate("D, j M Y H:i:s", $lastModifiedTimestamp)." GMT"; // Set headers header("HTTP/1.1 200 OK", true); header("Cache-Control: public, max-age=$maxAge", true); header("Last-Modified: $date", true); } } 

Comments

8
<?php header("Expires: Sat, 26 Jul 2020 05:00:00 GMT"); // Date in the future ?> 

Setting an expiration date for the cached page is one useful way to cache it on the client side.

1 Comment

Good, and using session_cache_limiter and session_cache_expire already taked care of this.
4

I was doing JSON caching at the server coming from Facebook feed nothing was working until I put flush and hid error reporting. I know this is not ideal code, but wanted a quick fix.

error_reporting(0); $headers = apache_request_headers(); //print_r($headers); $timestamp = time(); $tsstring = gmdate('D, d M Y H:i:s ', $timestamp) . 'GMT'; $etag = md5($timestamp); header("Last-Modified: $tsstring"); header("ETag: \"{$etag}\""); header('Expires: Thu, 01-Jan-70 00:00:01 GMT'); if(isset($headers['If-Modified-Since'])) { //echo 'set modified header'; if(intval(time()) - intval(strtotime($headers['IF-MODIFIED-SINCE'])) < 300) { header('HTTP/1.1 304 Not Modified'); exit(); } } flush(); //JSON OP HERE 

This worked very well.

1 Comment

It may not work if you don't set field name $headers['If-Modified-Since'] to uppercase -> $headers['IF-MODIFIED-SINCE]

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.