These days, with broadband connections the norm, we don’t need to worry as much about internet speeds or the filesize of our pages. However, that’s not to say that we still shouldn’t do so. If you wish to reduce the load times on your server, decrease the number of HTTP requests, and go that extra bit for your visitors, there are a few techniques that you can use. This tutorial covers a number of PHP tricks, including caching and compression.
1. CSS Amalgamation with PHP
As web developers, we often split up our CSS between several separate files to keep a logical separation and to make modifications easier. However, this increases the number of requests to the server, resulting in a slower page load. Using some PHP we can have the best of both worlds; keeping multiple files on our end, and using one request to retrieve all of them.

Preparation
Before we can optimize CSS files, we will need some CSS to work with! So let’s make three files and put some CSS in them.
// main.css
// Just some sample CSS
body {
width: 800px;
margin: 0 auto;
color: grey;
}
#wrapper {
margin-top: 30px;
background: url(../images/cats.png);
}
// typography.css
// Just some sample CSS
body {
font-family: Arial, san-serif;
font-weight: bold;
}
strong {
font-size: 120%;
}
// forms.css
// Just some sample CSS
form {
position: relative;
top: 400px;
z-index: 99;
}
input {
height: 50px;
width: 400px;
}
The PHP
We need to get the contents of these files and append them to each other in a specified order. So our script has to receive the names of the CSS files via URL parameters, open all the files and put them together. An explanation of the code follows.
<?php
//Lets define some useful variables
// --- NOTE: PATHS NEED TRAILING SLASH ---
$cssPath = './css/';
if (isset($_GET['q'])) {
$files = $_GET['q'];
// Got the array of files!
//Lets just make sure that the files don't contain any nasty characters.
foreach ($files as $key => $file) {
$files[$key] = str_replace(array('/', '\\', '.'), '', $file);
}
$cssData = '';
foreach ($files as $file) {
$cssFileName = $cssPath . $file . '.css';
$fileHandle = fopen($cssFileName, 'r');
$cssData .= "\n" . fread($fileHandle, filesize($cssFileName));
fclose($fileHandle);
}
}
// Tell the browser that we have a CSS file and send the data.
header("Content-type: text/css");
if (isset($cssData)) {
echo $cssData;
echo "\n\n// Generated: " . date("r");
} else {
echo "// Files not avalable or no files specified.";
}
?>
Breaking it Down
It looks quite complicated, but stick with me, it’s really pretty simple.
<?php
//Lets define some usefull variables
// --- NOTE: PATHS NEED TRAILING SLASH ---
$cssPath = './css/';
if (isset($_GET['q'])) {
$files = $_GET['q'];
// Got the array of files!
//Lets just make sure that the files don't contain any nasty charactors.
foreach ($files as $key => $file) {
$files[$key] = str_replace(array('/', '\\', '.'), '', $file);
}
This chunk of code sets the path for the CSS folder and checks that we have been sent some files to work with. The CSS path needs to have trailing slashes otherwise we will find ourselves with bucket-loads of errors. If we wanted, we could check automatically for a slash and add it if required. However, for the sake of simplicity I omitted that behavior.
Next we check each filename and remove any full stops and/or slashes. This prevents people from navigating the filesystem by passing filenames such as ‘../../secret/file’.
$cssData = '';
foreach ($files as $file) {
$cssFileName = $cssPath . $file . '.css';
$fileHandle = fopen($cssFileName, 'r');
$cssData .= "\n" . fread($fileHandle, filesize($cssFileName));
fclose($fileHandle);
}
}
Now we have to build our CSS data from the individual files. To do this, we loop through the files array with foreach, open each file and append the contents onto our data. The “\n” simply adds a new line character to keep things nice and tidy. The filesize() function is used to find the length of the file so that we can tell fread() how much we want (the entire file).
// Tell the browser that we have a CSS file and send the data.
header("Content-type: text/css");
if (isset($cssData)) {
echo $cssData;
echo "\n\n// Generated: " . date("r");
} else {
echo "// Files not avalable or no files specified.";
}
?>
The last bit of the script is to send the CSS data to the browser. This means we have to tell PHP that we are sending CSS data, and that it should inform the browser. We do this with the header function, setting the content type to ‘text/css’. Then we send the CSS to the client. We first check if there is any CSS data to send. If there isn’t, then this means that no names of CSS files were sent. If this is the case we simply reply with a CSS comment saying so. If, however, we do have some data to send, then we send that and add a message detailing when it was generated. If you wanted to, for example, add some copyright information to all your CSS in one go, then this would be an ideal place.
Putting it to the Test
Okay, now it’s time to test the script; we need to first build a directory structure and then place our script and CSS files. Have a look at the image below and try to replicate that structure. If you want something different, don’t forget to change the paths to reflect those changes.

Once everything is in the right place, we can test our script. The directory structure will have to be placed in the ‘htdocs’ or ‘www’ folder of a webserver with PHP (pretty much any webserver these days). Navigate to the index.php file. You should be greeted by a single comment: ‘Files not available or no files specified’. This means that we have not given any files for it to pull together. However, the good news is that this is a valid CSS comment and won’t cause any problems.
Let’s give something a little trickier a go; type in ‘index.php?q[]=main’, you should get the CSS from you main.css file and a notice at the bottom.
If we want to pull multiple files together (as this was really the entire point of the script) we can send this request: ‘index.php?q[]=main&q[]=forms’. As you can see we can repeat ‘q[]=’ as many times as we want because it is adding each value to an array. You could potentially add 50 CSS files together if you wanted using this script.
Concluding
Using this method can be very useful, and can provide benefits such as being able to have a default style sheet for every page and and an extra CSS file for pages with forms. It’s also easy to implement if you’re already using some sort of CSS processing with PHP. If you want, you can even rename index.php to index.css as long as you set up .htaccess to treat CSS files as PHP.
You might notice that I’m treating different orders of CSS files as different. This is because you may wish to have one stylesheet override another and therefore the order of the files is important. If this isn’t a problem for you, you may wish to perform a sorting function on the files array before processing.
Just a word of caution; if you place the index.php file in any folder other than the one that contains the CSS then you have to write your relative background image paths as if index.php was your stylesheet. This is because that’s what the browser thinks it is. Alternatively, you could add some code to rewrite these URLs, however, that is beyond the scope of this tutorial.
2. Stripping Whitespace from your HTML and CSS
Many of us use large amounts of whitespace when writing code. The good news is that whitespace in PHP doesn’t actually get sent to the browser. However, it does in HTML.
Browsers tend to only display one space no matter how many tabs you use in your code. This means that there is some wasted bandwidth. However, with some simple PHP we can remove this bandwidth leeching whitespace.
Preparation
Once again, we will need some raw data to work with; so copy the following example HTML and CSS code. Save the following into a .htm and a .css file in a folder within your server’s webroot directory.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Hey a Page!</title> <link rel="stylesheet" href="./css.css" type="text/css"> </head> <body id="homepage"> <div id="wrapper"> <div id="header"> <h1>Kittens for sale!</h1> <div> There are lots of spaces here! But we need to get rid of them! </div> </div> <div id="mainbody"> Lorem Ipsum dol... </div> </div> </body> </html>
body {
min-height: 800px;
background: black;
font-size: 18px;
}
#wrapper {
width: 960px;
margin: 20px auto;
padding: 15px;
}
#header h1 {
text-indent: -99999em;
background: url(../images/header.png);
display: block;
width: 100%;
height: 48px;
}
#mainbody {
font-weight: bold;
}
The PHP
One of the advantages of this method is that the same script will work with both HTML and CSS. Our script has to accept a filename as part of the request. Once the file has been loaded, it has to strip all whitespace down to just one space character. This is because we don’t want to remove all the spaces between words!
Once again, ther’s a bunch of PHP here, but I will go through it carefully with you.
<?php
$fileDirectory = '';
$file = $_GET['q'];
$nameExplode = explode('.', $file);
$ext = $nameExplode[1];
$fileName = $fileDirectory . $file;
if ($ext != 'css' AND $ext != 'htm' AND $ext != 'html') {
//Check for evil people...
die('Hackers...!');
} else {
//Lets get down to business
$handle = fopen($fileName, 'r');
$fileData = fread($handle, filesize($fileName));
//Now for some regex wizzardry!
$newData = preg_replace('/\s+/', ' ', $fileData);
fclose($handle);
//Time to output the data.
if ($ext == 'css') {
header("Content-type: text/css");
}
echo $newData;
}
?>
Having a Closer Look
This one isn’t so tricky, but we will still break it up and make sure we understand what is going on.
We are getting the filename via a parameter passed with the GET request and checking to make sure that it is an allowed filetype. Then we proceed to fetch the data and process it to remove excess whitespace. This method is relatively primitive and won’t remove all unnecessary whitespace, but it will deal with most of it in only a few lines of code!
<?php
$fileDirectory = '';
$file = $_GET['q'];
$nameExplode = explode('.', $file);
$ext = $nameExplode[1];
$fileName = $fileDirectory . $file;
This snippet just sets some variables. Once again, we are passing our data through ‘q’ as it is nice and short. This also gives us a place to define our directory for files and extract the file extension. The explode() function rips the filename up whenever it sees a ‘.’ and puts the bits into an array.
if ($ext != 'css' AND $ext != 'htm' AND $ext != 'html') {
//Check for evil people...
die('Hackers...!');
} else {
Here we’re checking to make sure that the file is either CSS or HTML. If it was something else we might find ourselves giving hackers a hole into our site like showing them settings.php! So after giving the hackers the flick we can move on to processing our data!
//Lets get down to business
$handle = fopen($fileName, 'r');
$fileData = fread($handle, filesize($fileName));
//Now for some regex wizzardry!
$newData = preg_replace('/\s+/', ' ', $fileData);
fclose($handle);
//Time to output the data.
if ($ext == 'css') {
header("Content-type: text/css");
}
echo $newData;
}
?>
Now for the main attraction; all we are really doing here is opening the file and reading it – like we did in the first script – and then ripping out as much whitespace as possible. This is achieved through a relatively simple regular expression that searches through the file for any spaces, tabs or newlines and then replaces them with a single space.
Lastly we send back the data, setting the required headers if we are dealing with CSS.
But Does it Work?
If you go into your browser and navigate to ‘index.php?q=css.css’ we should see one line of CSS across the page. This shows that everything is fine! We can also see the same effect on the source code for the html example. In fact in that small example, we reduced a 314 character CSS file down to 277 characters and a 528 character html file down to 448 characters. Not bad for 15 lines of code.
Conclusion
So that’s a good example of how we can do quite a lot with very little work. If you have a look at the source of pages like Google you will find that they have almost no whitespace because, when you receive millions of requests, a few extra kilobytes per request really adds up. Unfortunately, most of us aren’t that lucky!
3. Caching in your PHP Scripts
In this part, I will show you how to ‘retrofit’ caching into your scripts using the above script as an example. The aim is to speed things up by not having to regenerate the data every time someone requests a file. Generating the content every request is just a waste, especially on static data such as our CSS.
To add caching we need to add three things to our script. Firstly, we have to collect the data input to the script and generate a filename unique to that set of inputs. Secondly, we have to look for a cache file and see if it is sufficiently recent. Lastly, we have to either use the cached copy or generate new content and cache it for next time.

Breaking the Flow
This part of the process really depends on the individual script, however I will show where I am going to break the flow of this script for the caching.
<?php
$fileDirectory = '';
$file = $_GET['q'];
$nameExplode = explode('.', $file);
$ext = $nameExplode[1];
$fileName = $fileDirectory . $file;
//-- WE HAVE ENOUGH DATA TO GENERATE A CACHE FILE NAME HERE --
if ($ext != 'css' AND $ext != 'htm' AND $ext != 'html') {
//Check for evil people...
die('Hackers...!');
} else {
//-- WE CAN INTERCEPT AND CHECH FOR THE CACHED VERSION HERE --
//Lets get down to business
$handle = fopen($fileName, 'r');
$fileData = fread($handle, filesize($fileName));
//Now for some regex wizardry!
$newData = preg_replace('/\s+/', ' ', $fileData);
fclose($handle);
//Time to output the data.
//-- NOW WE CAN STORE THE NEW DATA IF REQUIRED AND OUTPUT THE DATA --
if ($ext == 'css') {
header("Content-type: text/css");
}
echo $newData;
}
?>
Putting it into Action
We will now actually write the code for caching into this script. I will first show the script completed and then go through each piece.
<?php
$fileDirectory = '';
$file = $_GET['q'];
$nameExplode = explode('.', $file);
$ext = $nameExplode[1];
$fileName = $fileDirectory . $file;
$cacheName = './cache/' . $nameExplode[0] . $nameExplode[1] . '.tmp';
if ($ext != 'css' AND $ext != 'htm' AND $ext != 'html') {
//Check for evil people...
print_r($ext);
die('Hackers...!');
} else {
if (file_exists($cacheName) AND filemtime($cacheName) > (time() - 86400)) {
$cacheHandle = fopen($cacheName, 'r');
$newData = fread($cacheHandle, filesize($cacheName));
fclose($cacheHandle);
$isCached = TRUE;
} else {
//Lets get down to business
$handle = fopen($fileName, 'r');
$fileData = fread($handle, filesize($fileName));
//Now for some regex wizardry!
$newData = preg_replace('/\s+/', ' ', $fileData);
fclose($handle);
//Lets cache!
$cacheHandle = fopen($cacheName, 'w+');
fwrite($cacheHandle, $newData);
fclose($cacheHandle);
$isCached = FALSE;
}
//Time to output the data.
if ($ext == 'css') {
header("Content-type: text/css");
if ($isCached) {
echo "// Retrieved from cache file. \n";
}
} else {
if ($isCached) {
echo '<!-- Retrieved from cache file. -->';
}
}
echo $newData;
}
?>
The Explanation
This one’s a bit trickier and a little more likely to leave you scratching you head. But don’t worry, not much has changed and we will go through each section. An extra feature we have included is the refreshing of the cache every 24 hours. This is handy so if you change anything, you can either wait 24 hours or simply empty the cache directory. If you want a different refresh interval just calculate it in seconds.
$cacheName = './cache/' . $nameExplode[0] . $nameExplode[1] . '.tmp';
This bit of code just gets the file’s name and extension, glues them together and adds the cache directory and the appropriate ‘.tmp’ extension.
if (file_exists($cacheName) AND filemtime($cacheName) > (time() - 86400)) {
$cacheHandle = fopen($cacheName, 'r');
$newData = fread($cacheHandle, filesize($cacheName));
fclose($cacheHandle);
$isCached = TRUE;
} else {
Here we’re checking if we have a cache file saved and if the cache file was created within 24 hours. If both these conditions are met then we open the file and extract its contents to substitute for the scripts output. We also set $isCached to true so we can output some messages at the end.
//Lets cache! $cacheHandle = fopen($cacheName, 'w+'); fwrite($cacheHandle, $newData); fclose($cacheHandle); $isCache = FALSE; }
Now we are caching the output of the script for us to use in later requests. We simply open a file in write mode, dump our data into it and then close it. Strictly you don’t have to close files in PHP but it’s considered a good practise so I have done it here.
//Time to output the data.
if ($ext == 'css') {
header("Content-type: text/css");
if ($isCached) {
echo "// Retrieved from cache file. \n";
}
} else {
if ($isCached) {
echo '<!-- Retrieved from cache file. -->';
}
}
This is another part of the script that was modified a little so that we can offer some feedback through the browser. If the file was retrieved from the cache we can add a message to the script’s output. Notice that the message for CSS scripts has ‘\n’ at the end. This is because the characters ‘//’ comment our entire line and ‘\n’ pushes everything else onto another line. If you want to disable the messages all you have to do is comment out the line ‘$isCached = TRUE;’.
Giving it a Whirl
If we use our script again, we will notice no change until we refresh a second time when we will see a message saying that the file was retrieved from cache. Sweet success! This caching setup can also be applied to the first script with little modification, however, that is left as an exercise for the reader.
Concluding
Being able to quickly add simple but effective caching to any script that you are working on is an extremely useful skill. It just adds that extra bit to the script, reducing the load on your server and speeding up the site for users. Now that’s win-win!
Summing it Up
In this tutorial I have shown you a few handy but simple ways to speed up your site with a dash of PHP. I really hope that you find them useful and that you can apply them to a project in the future. How do you improve your site’s performance?
- Follow us on Twitter, or subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.

Cool, isn’t this what Drupal does?
You tutorial has a serious secury breach.
You can’t just validate the file extension the way you do.
What about a file called: exploit.html.php
A file with this name will validate because the index 1 will be html
http://pt2.php.net/manual/en/security.filesystem.php
Sorry about typos…
Just make sure those files are in a different directory. That protection is mostly to top people from trying index.php?q=”./index.php”
This article has to be some of the worst advice for optimization that I have ever seen.
It’s unbeleivable that you where able to get 150$ for this utter nonsense!
Making a PHP script to serve CSS files is NOT a way top optimize – On the contrary! You introduce wuite a lot fo extra load on the server and response time this way – You don’t do ANYTHING good – Thats just plain STUPID!
The only sound advice in this article is caching, and even that you don’t cover proberly!
Do us all a favor – If you don’t really know the subject you are talking about – Don’t say anything, and at least don’t write an article giving people incredibly stupid and insane advice!
I’ve been working with PHP for 12 years… I know my stuff and this is INSANE!
I’m doubtful about the idea of combining css files using php, or even single css.php files. Am I correct in thinking that because the css file is generated dynamically, unless you want to change the server caching time on php scripts your css.php file will always return a 200 header, therefore sending the file to the client every time a page is loaded on your site? Whereas a single static .css file would be retained in the client’s cache from one page view to another.
I think it’s slower to strip whitespaces then to load them via HTTP. Maybe if you compile static files once, minify them, and then put them to production.
I prefer including external files without using server side scripts.
Because that is the standard way
and
For clean code purposes.
You should really provide a cache header for your compressed css files, if they don’t change a lot you’re best guess woulg be to use something like this:
header(‘Cache-Control: public’);
header(‘Expires: ‘ . gmdate(‘D, d M Y H:i:s’, time() + 60 * 60) . ‘ GMT’);
This will cache the response for an hour without having the client browser making any request to the server in order to validate the cached data. This helps to reduce the number of HTTP requests a lot.
There are some good basic ideas here, but imho they add too much complexity for minimal gains.
I manage heavily trafficked sites that have the potential for heavy processor load, file access and bandwidth usage. In a nutshell we use similar techniques but try our best to keep the details away from the developers and out of the code.
gzip from the server is the best way to keep bandwidth usage down, that and proper cache control with the browser.
memcached is a fantastic product that lets us cache content at several levels. We cache html output objects and database returns using memcached and have reduced load on the database by 85% and load on the hard drives by a similar amount. This takes a good amount of work and does have a presence in the codebase but the payoffs are tremendous.
nginx/fastcgi instead of apache, look it up. We reduced our processor load and memory footprint by a ton and the response times are fantastic. The savings in memory go to a larger local memcache.
Finally, we use SSL terminators so the servers don’t spend any time encrypting, I recommend the Webmux line.
When optimizing, you need to start by benchmarking and profiling the existing setup. There are four basic bottlenecks, throughput (bandwidth), database response, CPU and Filesystem I/O. The database, if on a separate system (should be) should be measured for CPU and I/O as well. You should also profile your queries, you’d be surprised at how many queries can be generated just generating simple navigation elements, comments, who’s online etc. These are all cache-able.
Our goal is to touch the database for updates and inserts and serve queries and files from the cache. Users tend to do the same thing and view the same content. there’s no reason to tax the system retrieving commonly used objects, use RAM for that.
Thanks for the tips Thomas!
Really great tips. Thanks a lot!
Great tut, thx!!!
I’m sorry to tell you man, but as speeding up website goes.. you don’t seem to know anything..
With all the three tips you provided, there’s much better alternatives.
Tip 1 and 2:
There are tools that can minify the css files, remove spaces and etc, and if you want, join them. This way you perform this when you are deploying these files to the server and don’t waste php and IO(even worse) doing this for every HTTP request which is really a performance killer.
What’s more, you can use gzip to further reduce the size of the files transmitted to the clients and set cache-control headers accordingly to possibly even save this transfer.
Tip 3:
My answer here is using memcached or any other caching mechanism available out there. With those kind of tools you can choose if you cache the queries result, the entire page or even some fraction of it and performance wise, these options perform way better than doing it manually.
I recommend you(and everyone) to read the Yahoo! performance team blog to understand how these things really work before fooling a loot of people with this kind of tutorial.
The title of this tut was “3 Ways to Speed up Your Site with *PHP*”. Not “3 Ways to Speed up Your Site without PHP”
Interesting ideas, not sure if they make things faster most of the time though since it’s based on a request.
If you are not super advanced with other methods, taking the css optimization script and running it as a cron job might be smarter. Then if you want to force a css update before your cron runs (let’s say it runs daily), you could manually run that script too to have an immediate update. Otherwise just have it run daily to pick up small changes to css by your team, maybe at 4 a.m. when load is lowest or whatever.
This system is good in the fact that changes have been made to the way the css file is passed, it is comipled & served as a file which is used to create our content.
It also makes use of css rewrites and most browsers support this.
OLD:
NEW: Strong
The new tag now has some new css which is used when the tag is declared in our new document.
I dont get you…
lol thats only because the html tags i used got cut out as well as half of my post when i submit the comment.
What i am trying to say is that this system makes use of up to date css standards which are accepted by most browsers?
What I’ve noticed is that combining all the javascript files into a single request (similar to CSS amalgamation) speeds up your site the most.
Also, setting a far expires header to the combined scripts and css files has a huge positive impact
please can you tell why , when i copy this code and run it with my site its not working , i dont no is it because the local server or what ?
http://www.al-ansaar.net
Also something this post don’t talk about is compression. It is something to look into if your using PHP.
Nice Code
Nice code? It’s pretty bad and pointless. It only adds overhead.
Quite confusing! Could have been simple! Please emphasize on quality than quantity!
“1. CSS Amalgamation with PHP” and “2. Stripping Whitespace from your HTML and CSS” are awesome but number 3. is confusing.
I have always thought our main site is too slow so this is a great tutorial for us! Thanks.!
I built a simple function to load Javascript/CSS whenever I need it. I’m using CodeIgniter.
MY VIEW:
$data['external_files'] = $this->booking->external_files($array = array(‘css_screen’, ‘js_jq’, ‘js_jq_tablesorter’, ‘js_jq_datepicker’, ‘js_jq_colorbox’, ‘js_jq_ui_core’, ‘js_jq_ui_tabs’));
$this->load->view(‘common/user_header’, $data);
MY FUNCTION:
function external_files($array)
{
$result = ”;
if (in_array(“css_screen”, $array)):
$result .= ”.”\n”;
endif;
if (in_array(“js_jq”, $array)):
$result .= ”.”\n”;
endif;
etc…
This will cartainly be very helpful with my upcoming websites, which use a lot of different javascript and css files. Thanks
en,about css amalgamation, if i want to cache css file in the client browers, it is difficult and the modern browers will not cache php so css amalgamation with php will make the browers not cache css
“en,about css amalgamation, if i want to cache css file in the client browers, it is difficult and the modern browers will not cache php so css amalgamation with php will make the browers not cache css”
That’s not true. It all depends what headers you send.
There is no reason to use PHP here.
Client -> WebServer -> static file
is faster then
Client -> WebServer -> PHP -> static file
Even with several files. There is Client’s cache, WebServer cache, filesystem cache, or even keepalive option on WebServer.
So this is bad idea to use PHP here.
Thanks for the stuff but i think its also important to code css “browser compatible”.
Check this: https://developer.mozilla.org/en/Writing_Efficient_CSS
Thanks, This post.
Thanks for the ideas. I will try this on my site I currently working on, because I usually use gzip from php.
This is really bad stuff! You should avoid using any of these techniques! Just turn on gzip and opcode caching on your server if you want to speed up your site!
Well,. the CSS amalgamation code is ain’t working for me. Where should I place the PHP code in my webpage
good article….
Anyone know how to speed the performance of a page with a gazillion tiny includes in it?
http://decksanfrancisco.com/prices/san-francisco-decking-prices.php for example has 45 tiny (8 characters or less) includes from another domain on it. It takes like 12 seconds to load some times, depending on how busy my server is.
Not good.
Yes they have to be included from a central location as they are used by dozens of web sites and need to be changed from time to time.
@Mojo. I think that Drupal has some of this features (css file aggregation and caching).. but they are turned off by default..
great tutorial
Thanks!! Great tutorial.
Great Tutorial..!