Using HTTP Compression in PHP: Make Your Web Pages Load Faster

Web developers are always looking for ways to reduce the loading time of their pages. This article, the first of three parts, shows you how to make PHP pages load faster by showing you how to compress dynamic PHP pages. Techniques covered include using PHP’s built-in “gzencode()” function, along with output buffering control functions.

Introduction

Due to its inherent versatility, PHP provides developers with the ability to develop different mechanisms for reducing the loading time of parsed PHP pages. When it comes to optimizing methods for making PHP pages load faster, I covered the topic across several articles, which have been previously published right here, on the prestigious Developer Shed network, ranging from how to develop an (X)HTML caching system, to implementing a MySQL result set caching mechanism.

Of course, reducing the load time of parsed PHP pages isn’t limited to developing caching systems of a different nature. There are many other techniques that can be used, either as standalone or integrated solutions, in order to tackle some performance issues and deliver quickly dynamic content.

Admittedly, making web pages (static or dynamic ones) load quicker is actually a fine-tuning process, which involves not only using the caching methods that I mentioned before, but implementing other well-known approaches. These include optimizing CSS, JavaScript and Flash files, compressing images, and eventually the optimization of additional elements that might be requested together with a web document, like Java applets.

While the previous optimization methods are used frequently during the development of a Web application, in order to reduce the amount of data transmitted across different HTTP requests, in general terms caching systems are aimed at decreasing the frequency that data is sent through a specific network (local networks, intranets, extranets or the Internet as a whole). However, are these techniques the only ones used to optimize web pages? Fortunately, the answer is a resounding No!

Just in case you weren’t aware of it, a few years ago most modern browsers began to support HTTP compression as a transparent layer. This can be extremely useful when looking for additional ways to reduce the amount of data transmitted between a Web server and a client. As a result, this technique also can be taken into account if you want to make your PHP pages load considerably faster.

Since PHP offers a powerful built-in library for handling HTTP compressed data, over this series I’ll explain the basics of working with HTTP-compressed PHP pages. I’ll illustrate, with several code samples, different methods for compressing dynamic PHP pages. So, do you want to learn how to take advantage of HTTP compression in your own PHP applications? Right, start reading to find out how this is done!

{mospagebreak title=The basics of data compression: writing a simple “crunching” PHP script}

A good place to start explaining how you can use HTTP compression within your scripts is by first developing a simple example. This one only “crunches” the dynamic output of a PHP file, by removing all the new lines from it after the file has been parsed.

Bearing in mind this concept, please take a look at the definition of the “sample_file.php” file, which displays some database records from a “users” database table:

// include MySQL class file
require_once ‘mysqlclass.php’;
// connect to MySQL
$db=&new MySQL(array(‘host’=>’host’,'user’=>’user’,'password’=>’password’,
‘database’=>’database’));
// run SQL query
$result=$db->query(‘SELECT * FROM users’);
// display results
while($row=$result->fetch()){
    echo $row['id'].$row['name'].$row['email'].’<br />’;
}

As you can see, the above script also uses a few additional MySQL-related classes, thus for the sake of completeness, here’s the signature for them, even though they’ve been shown in previous articles:

class MySQL {
    var $conId; // connection identifier
    var $host; // MySQL host
    var $user; // MySQL username
    var $password; // MySQL password
    var $database; // MySQL database
    // constructor
    function MySQL($options=array()){
        // validate incoming parameters
        if(count($options)>0){
            foreach($options as $parameter=>$value){
                if(empty($value)){
                    trigger_error(‘Invalid parameter
‘.$parameter,E_USER_ERROR);
                }
                $this->{$parameter}=$value;
            }
            // connect to MySQL
            $this->connectDB();
        }
        else {
            trigger_error(‘No connection parameters were
provided’,E_USER_ERROR);
        }
    }
    // connect to MYSQL server and select database
    function connectDB(){
        if(!$this->conId=mysql_connect($this->host,$this-
>user,$this->password)){
            trigger_error(‘Error connecting to the
server’,E_USER_ERROR);
        }
        if(!mysql_select_db($this->database,$this->conId)){
            trigger_error(‘Error selecting
database’,E_USER_ERROR);
        }
    }
    // perform query
    function query($query){
        if(!$this->result=mysql_query($query,$this->conId)){
            trigger_error(‘Error performing query
‘.$query,E_USER_ERROR);
        }
        // return new Result object
        return new Result($this,$this->result); 
    }
}

class Result {
    var $mysql; // instance of MySQL object
    var $result; // result set
    function Result(&$mysql,$result){
        $this->mysql=&$mysql;
        $this->result=$result;
    }
    // fetch row
    function fetchRow(){
        return mysql_fetch_array($this->result,MYSQL_ASSOC);
    }
    // count rows
    function countRows(){
        if(!$rows=mysql_num_rows($this->result)){
            return false;
        }
        return $rows;
    }
    // count affected rows
    function countAffectedRows(){
        if(!$rows=mysql_affected_rows($this->mysql->conId)){
            trigger_error(‘Error counting affected
rows’,E_USER_ERROR);
        }
        return $rows;
    }
    // get ID from last inserted row
    function getInsertID(){
        if(!$id=mysql_insert_id($this->mysql->conId)){
            trigger_error(‘Error getting ID’,E_USER_ERROR);
        }
        return $id;
    }
    // seek row
    function seekRow($row=0){
        if(!mysql_data_seek($this->result,$row)){
            trigger_error(‘Error seeking data’,E_USER_ERROR);
        }
    }
    function getQueryResource(){
        return $this->result;
    }
}

Right, after listing the classes I used on my previous script, now let me show you how to create a simple snippet that removes new line characters from the corresponding output of the above “sample_file.php” file. Here’s the script that does precisely that:

$file=’sample_file.php’;
// start output buffer
ob_start();
// parse PHP file
include($file);
// get ‘crunched’ buffer contents
$contents=preg_replace(“/(rn|n)/”,”",ob_get_contents());
// close output buffer
ob_end_clean();
// display file contents
echo $contents;

First of all, I’ll highlight a few interesting things regarding the above script. Notice how I used some of PHP’s built-in output buffering functions, in order to parse the corresponding sample file and store its “crunched” contents in the $contents variable. This method should clearly demonstrate how useful output buffers can be for applying post processing to parsed files (caching (X)HTML output is another handy implementation of output buffering functions).

As you can see, the logic I applied here is very simple. By removing some white space from the output generated by the previous sample file I reduced its size. In this way I implemented a pseudo compression method, which to be honest is still far from real HTTP compression. However, since this is only my first approach to the problem, it’s not so bad at all.

Well, you hopefully learned a basic method for reducing the size of dynamic PHP pages, by using the post-processing capabilities of output buffering. Now, it’s time to move on and see how to use real HTTP compression within parsed PHP files. Please keep reading.

{mospagebreak title=Moving one step forward: using real HTTP compression on parsed PHP files}

Once I demonstrated how to eliminate some redundant data from previously parsed PHP files, I could go one step forward and write a simple PHP function, based on the script that you saw in the previous section. This function looks like this:

function getCompressedData($data){
    ob_start();
    // check to see if $data is a file
    if(file_exists($data)){
        // include file into output buffer
        include($data);
    }
    else{
        // echo string to output buffer
        echo $data;
    }
    // crunch buffer data
    $data=preg_replace(“/(rn|n)/”,”",ob_get_contents());
    // clean up output buffer
    ob_end_clean();
    // return data
    return $data;
}

And eventually the above function could be called as follows:

// display crunched data
echo getCompressedData(‘sample_file.php’);

As shown above, the “getCompressedData()” function implements the same logic for reducing the size of input data, and additionally includes the capability to determine whether the corresponding incoming argument is a file or not. In either case, data is crunched by using the same technique that I discussed before, and finally is echoed to the browser.

So far, the above approach is merely a rough method for optimizing parsed PHP files. The best part is that the prior sample code might be easily modified, in order to apply “Gzip” HTTP compression on the output generated by the previous “sample_file.php” file. Considering this useful concept, a basic script that compresses a specific parsed PHP file, could be coded like this:

// start output buffer
ob_start();
// include file contents
include(‘sample_file.php’);
$contents=ob_get_contents();
// close output buffer
ob_end_clean();
// compress data by gzip
$contents=gzencode($contents,9);
// send http header
header(‘Content-Encoding: gzip’);
// display data
echo $contents;

Although the above script maintains the same general structure that you saw before, in fact it exposes a few remarkable changes that make it work very differently. First of all, the script begins opening a new output buffer and parses the corresponding “sample_file.php” file. Then, the respective output is fetched and stored in the $contents variable, and finally the buffer is cleaned up by the “ob_end_clean()” function.

As you may have guessed, the next few lines of the script perform true “Gzip” compression on the data by using the PHP built-in “gzencode()” function (the second argument, in this case “9″, means the highest level of compression) and by sending the appropriate HTTP header to the browser, in order to indicate that data will be compressed and then transmitted in turn. On the client side, the browser should decompress the data back to its original state and display the contents. Simple and sweet.

At this point, I hope you learned how “Gzip” compression can be used to reduce the amount of data transmitted from the Web server to the client, after a given PHP file has been parsed. Reducing the amount of data transmitted, of course, reduces the download time. Therefore, I’d like to demonstrate how to use the functionality of a “data crunching” script, together with the natural ability of PHP to compress parsed files, in order to define a unique function that combines all of these advantages. Want to see how this function looks? Go ahead and read the next section.

{mospagebreak title=Compressing data by “Gzip:” defining the “getCompressedContent()” function}

In order to take advantage of the capabilities of PHP for using “Gzip” to compress dynamic pages, what I’ll do next is define a simple function which uses the built-in “gzencode()” function to deliver compressed data. The source code that corresponds to this function is listed below:

(*)function getCompressedContent($data){
  // start output buffer
  ob_start(); 
  // check to see if $data is a file
  if(file_exists($data)){
    // include file into output buffer
    include($data);
  }
  else
  {
    // echo string to output buffer
    echo $data;
  }

  // Get the current buffer and clean it, as well as remove any
  // newline/carrage return from the output
  $data = preg_replace(array(“/r/”, “/n/”), ”, ob_get_clean());

  // check if browser supports gzip encoding
  if(strstr($_SERVER['HTTP_ACCEPT_ENCODING'],’gzip’)){
    // crunch content & compress data with gzip
    $data=gzencode($data,9);

    // send http header
    header(‘Content-Encoding: gzip’);
  }

  // return data in the proper format
  return $data;
}

As you can see, the function defined above shows some significant code changes compared to the procedural script, listed in the previous section. Of course, the first thing worth noting here is the introduction of a checking block, useful for determining whether the browser offers support for “Gzip” encoding. If it does, the incoming data is stored in the output buffer, then gzip-compressed and finally returned to calling code.

In addition, it’s possible to add some “data crunching” capabilities to the above function, by replacing the following line:

$data=gzencode(ob_get_contents(),9);

with this one:

$data=gzencode(preg_replace(“/(rn|n)/”,”",ob_get_contents
()),9);

After the pertinent code modification, the signature for this function would be as follows:

/*
// improved function to compress dynamic pages and strings
// first crunches data and next compresses it by gzip
*/
function getCompressedContent($data){
    // check if browser supports gzip encoding
    if(strstr($_SERVER['HTTP_ACCEPT_ENCODING'],’gzip’)){
        // start output buffer
        ob_start();
        // check to see if $data is a file
        if(file_exists($data)){
            // include file into output buffer
            include($data);
        }
        else{
            // echo string to output buffer
            echo $data;
        }
        // crunch content & compress data with gzip
        $data=gzencode(preg_replace(“/
(rn|n)/”,”",ob_get_contents()),9);
        // clean up output buffer
        ob_end_clean();
        // send http header
        header(‘Content-Encoding: gzip’);
    }
    // return data
    return $data;
}

That’s it. Now, the “getCompressedContent()” function is capable of performing a “crunching” process on the input data, as well as compressing it by using “Gzip.” On the other hand, in case the browser doesn’t support “gzip” encoding, no compression process is applied to the respective data.

Now that this function has been appropriately defined, the “sample_file.php” file that you saw before can be passed to the function as shown below:

// display compressed data
echo getCompressedContent(‘sample_file.php’);

As you’ve seen by the sample codes that I wrote, compressing dynamic pages with PHP is not so difficult as it seems, allowing you to significantly accelerate the download time of parsed PHP files. Of course, as with other compression algorithms, benefits will vary, depending on the type and amount of data being compressed, so before using HTTP compression within your PHP scripts, make sure you’re getting real advantages.

To wrap up

As you know, this tutorial has now concluded. Over this first part of the series, I showed some simple yet effective approaches for compressing dynamic PHP pages, using essentially PHP’s built-in “gzencode()” function, along with output buffering control functions.

In the next article, I’ll show you how to use HTTP compression within an object-oriented development environment, reducing the overall download time of your PHP objects. You won’t want to miss it!

* Editor’s Note: Thanks to a very helpful reader we have made some changes to the code to correct a small bug.

Google+ Comments

Google+ Comments