12

Say I for some reason want to serve my CSS through PHP (because of pre-processing, merging, etc). What do I need to do in my PHP to make this work well? Other than the most obvious:

header('content-type: text/css; charset=utf-8');

What about headers related to caching, modification times, etags, etc? Which ones should I use, why and how? How would I parse incoming headers and respond appropriately (304 Not Modified for example)?


Note: I know this can be tricky and that it would be a lot easier to just do what I want to do with the CSS before I deploy it as a regular CSS file. If I wanted to do it that way, I wouldn't have asked this question. I'm curious to how to do this properly and would like to know. What I do or could do beforehand with the CSS is irrelevant; I just want to know how to serve it properly :)

Note 2: I really would like to know how to do this properly. I feel most of the activity on this question has turned into me defending why I would want to do this, rather than getting answers on how to do this. Would very much appreciate it if someone could answer my question rather than just suggesting things like SASS. I'm sure it's awesome, and I might try it out sometime, but that's not what I'm asking about now. I want to know how to serve CSS through PHP and learn how to deal with the caching and things like that properly.

Yahia
  • 69,653
  • 9
  • 115
  • 144
Svish
  • 152,914
  • 173
  • 462
  • 620
  • 2
    I fully support this cause, even if we do have CSS pre-processors or minifiers available that might be better suited to the task. At the very least, `readfile()` is much faster than `@import` for combining CSS files in a simple way, and people use `@import` all the time. – Wesley Murch Feb 09 '12 at 09:33
  • Thanks. Seems some actual answers have started appearing here now! *reads...* – Svish Feb 09 '12 at 11:04
  • @WesleyMurch Just so it stays clear for everyone doing this for that reason: proper preprocessors like LESS let you pre-compile several CSS files into a single one, so `@import` is a non-issue. – Camilo Martin Nov 16 '13 at 06:40

5 Answers5

6

A commendable effort. Caching gets way too little good will. Please enjoy my short prose attempting to help you on your way.

The summary

Sending an ETag and a Last-Modified header will enable the browser to send a If-Modified-Since and a If-None-Match header back to your server on subsequent requests. You may then, when applicable, respond with a 304 Not Modified HTTP status code and an empty body, i.e. Content-Length: 0. Including a Expires header will help you to serve fresh content one day when the content has indeed changed.

The apprentice

Sounds simple enough, but it can be a bit tricky to get just right. Luckily for us all, there is really good guidance available.

Once you get it up and running, please turn to REDbot to help you smooth out any rough corners you may have left in.

The expert

For the value of the ETag, you will want to have something you can reproduce, but will still change whenever the content does. Otherwise you will not be able to tell whether the incoming value matches or not. A good candidate for a reproducible value which still changes when the content does, is an MD5 hash of the mtime of the file being served through the cache. In your case, it would probably be a sum for all the files being merged.

For Last-Modified the logical answer is the actual mtime of the file being served. Why neglect the obvious. Or for a group of files, as in your case, use the most recent mtime in the bunch.

For Expires, simply choose an appropriate TTL, or time-to-live, for the asset. Add this number to the asset's mtime, or the value you chose for Last-Modified, and you have your answer.

You may also want to include Cache-Control headers to let possible proxies on the way know how to properly serve their clients.

The scholar

For a more concrete response to your question, please refer to these questions predating yours:

Community
  • 1
  • 1
nikc.org
  • 16,462
  • 6
  • 50
  • 83
  • Thanks! Lots to look into, which I definitely will. The REDbot tool seems incredibly handy too! – Svish Feb 09 '12 at 11:07
3

The easiest way to serve CSS (or JavaScript) through PHP would be to use Assetic, a super-useful PHP asset manager similar to Django's contrib.staticfiles or Ruby's Jammit. It handles caching and cache invalidation, dynamic minification, compression, and all the "tricky bits" that were mentioned in other answers.

To understand how to write your own asset server properly, I strongly recommend you read Assetic's source code. It's very commented and readable, and you'll learn a lot about best practices regarding caching, minification, and everything else that Assetic does so well.

Rodney Folz
  • 6,709
  • 2
  • 29
  • 38
2

One common patter is to include a meaningless GET parameter. In fact, stack exchange sites do exatly this:

<link ... href="http://cdn.sstatic.net/stackoverflow/all.css?v=0285b0392b5c">

The v (version) is presumably a hash of some kind, probably of the css file itself. They do not store the old sheets, it's just a way to force the browser to download the new file and not use the cached one.

With this setup, it is safe to set Cache-Control:max-age to a large value.

The ETag will make server reply 304 if the file is not modified, you might as well use the same hash:

header('ETag: "' . md5("path to css file") . '"');
Mikulas Dite
  • 7,790
  • 9
  • 59
  • 99
  • Is the ETag handled transparently by the webserver though? When the browser does a new request for this css file, who deals with it? Who replies with the 304? Do I have to check some headers or something in my code and check if the hash is the same or something? – Svish Feb 08 '12 at 14:40
  • @Svish As long as your php script serves as proxy, even if the underlying server does generate ETag, it does not relate to the css file in any way. The server should reply 304 when client send up to date `ETag`, that's also what the php script ought to do. – Mikulas Dite Feb 08 '12 at 14:59
  • 1
    For what it's worth, the `v` parameter seems to be the first 12 hex digits of the SHA-1 signature for the file. – Camilo Martin Nov 16 '13 at 02:29
1

I just finished explaining here why I don't think PHP-processed CSS is a good idea; I believe most people who implement it would be better served by another application structure. Take a look.

If you must do it, making caching work will require keeping track of each variant independently and having the client send a parameter which uniquely identifies that variant (so you can say "not modified").

The Content-Type header is a good start, but not the tricky bit...

Community
  • 1
  • 1
Borealid
  • 95,191
  • 9
  • 106
  • 122
  • Totally know that's not the tricky bit, which is why I asked :) What do you mean by "keeping track of each variant"? Variants of what? What I'd like to do at the moment is just to merge and possibly minify several CSS files into one. There is no variation between logged in users or anything like that. One CSS for everyone. – Svish Feb 03 '12 at 14:06
  • 4
    @Svish If you're just using PHP for combining files and minification, then you should consider doing that in advance, instead of dynamically for every request. Your web server will thank you. – Borealid Feb 03 '12 at 14:07
  • I won't do it on every request, just if any of the CSS files have changed. Either way, why I want to do this isn't my issue here. I already know I can do this in advance. I want to know what headers to send to serve a CSS properly with caching and all, which is what I believe I asked? I appreciate the heads up on the issue, but that there are other ways to solve this is a bit irrelevant in my opinion... doesn't answer my question at least. – Svish Feb 03 '12 at 14:10
  • @Svish Then you're going to need more than headers. You'll also need to parse the request and reply with a `304 Not Modified` as appropriate. – Borealid Feb 03 '12 at 14:19
  • Didn't know about that! Added that to my question. Hopefully it's clearer now. – Svish Feb 03 '12 at 14:30
  • Things like that is exactly why I asked this question. I want to know how to do this properly :) – Svish Feb 03 '12 at 14:37
  • I used to do it like this - now I use SASS, it's far far far superior. – Rich Bradshaw Feb 03 '12 at 16:46
  • 1
    @RichBradshaw Give Stylus a gander. I used to use SASS too. – Borealid Feb 03 '12 at 16:47
  • Problem with SASS and things like that is that they often require Ruby or other things. I'd like to have a solution that can be used with plain PHP without having to install anything extra :) And also, I'd like to make it myself simply because I'd like to make it myself... to learn and have fun :) – Svish Feb 08 '12 at 21:31
  • @Svish Stylus and LESS are both implemented in Javascript. You don't need to put anything at all on the server if you don't want to - just send the JS file to the client machine along with the pre-CSS. You may use Node.js if you wish to compile on the server, or not if you don't. – Borealid Feb 08 '12 at 22:23
  • 1
    @Borealid Wouldn't that make my site look broken if Javascript was disabled? – Svish Feb 09 '12 at 08:46
  • 1
    @Svish Have you ever tried to browse the web with JavaScript disabled? Most sites are broken. Anyway, you can go the server-side compilation route if you want your styling to work without JS. – Borealid Feb 09 '12 at 13:57
0

You have to add query string at end of the javascript file, that is good option to say it is new file until that browsers are think same css files

www.example.com/css/tooltip.css?version1.0  

or

www.example.com/css/tooltip.css?12-01-2012

so browser is going to understand this new files it reloads again, keep it in cache up to next release,and easy to maintainable if you append automatic date using php at end of the query string.

Guru
  • 419
  • 4
  • 13