Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

3 Ways to Speed up Your Site with PHP

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

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?


Advertisement