In this article we will be discussing the compression of output from a php, not normal assets such as JS and css. Because the nature of PHP files is dynamic we have to use another method.
After needing to compress my PHP output for QWcrm I started researching on the internet after thinking it would just be a case of a couple of lines of code. What I discovered is there are several ways to compress PHP output and each has their pros and cons. I also found that some people were incorrectly using the wrong or outdated methods. Below I will go through each method I found and then I will sum up my thoughts at the end so you can easily workoout what method you want to use.
One other thing to note is that everyone goes on about how they gzip their content, but in these modern times there are 2 compression methos GZIP and DEFLATE, deflate being the newer method and can offer better compression.
.htaccess - (for static assets and PHP sometimes)
I have discovered that on some server installs that the following code (in particular text/html) will compress PHP output. Obviously you have to have mod_deflate installed. Worth a try and add other file types as needed.
<IfModule mod_deflate.c> <IfModule mod_filter.c> AddOutputFilterByType DEFLATE text/html </IfModule> </IfModule>
Below is the normal way of compressing with deflate (gzip replacement) and the legacy gzip. I have added these for references only as it keeps coming up but will not actually compress PHP output but as a combined effect can help reduce the download footprint of the webpage and its assets.
Example 1 - Enable compression via .htaccess (mod_deflate)
This seems to be the prefered .htaccess compression method now because it gets better ratios.
<IfModule mod_deflate.c> <IfModule mod_filter.c> AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript </IfModule> </IfModule>
Example 2 - Enable compression via .htaccess (mod_gzip)
http://www.awesomeinfolab.com/enable-gzip-compression/
I have never come across this method before in htaccess and might not work on all apache installs. I am thing of PHP as Fast-CGI/'Apache Module'
<ifModule mod_gzip.c> mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file .(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text/.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude mime ^image/.* mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* </ifModule>
Links
- How to enable gzip compression on Apache
- Active Gzip Compression | CSS-Tricks
- Enable Gzip compression for your website
- How to enable compression and gzip for page speed.
- Enable gzip compression | GTmetrix
- An introduction to gzip / mod_deflate - InMotion Hosting
ob_start("ob_gzhandler") - (Simple 1 liner)
This takes advantage of Php's buffered output and buffers all output of the script until the Php engine has completed, and then runs all the output through a special function that gzip compresses it before sending it on to the browser.
Just place this at the very top of all your PHP pages and it will send gzip-compressed output to the browsers with the correct headers.
ob_start("ob_gzhandler");
The function basically says start buffering PHP content and tag it to says the outputted content should be gzipped. The procedure should also send the correct headers so the browser knows it is compressed. I also think that unless the browser has told the server that it supports compression that the content will be returned uncompressed. All modern browsers send the 'I support compression' headers.
Some say once the script is finished it will flush the buffer and output the content automatically and that is why you can get away with 1 line, however this article from magicmonster tells you to add the flush command at the end to flush the cache.
ob_end_flush();
The method mentioned above is quick and easy, but the downfalls are that it only works on Apache with mod_gzip and according to the Php manual this is not the preferred method for gzipping.
You can increase the compression ratio by altering the php.ini or add the following to the script before ob_start("ob_gzhandler")
ini_set('zlib.output_compression_level', 4);
Another example
<?php if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start(); ?>
This example adds a check to see if the browser send the “Accept-Encoding: gzip” or “deflate” header before enabling the compression, however the buffer is still enabled even if compression is not required.
(From the Php manual at www.php.net)
note that using zlib.output_compression is preferred over ob_gzhandler().
Links
- How to enable gzip compression with Php - Excellent article I got some of this information from
- PHP: ob_gzhandler - Manual
- Using gzip compression on PHP pages (magicmonster) - This is a really easy to read and following article.
- Fast, Effective PHP Compression | Perishable Press - The methods might not work on all Apache/PHP installations.
Full PHP buffer
Create a buffer, fill the buffer, compress the buffer data and send the content to the client
This is similiar to ob_start("ob_gzhandler") but there is a little more to it.
Example 1
http://www.webcodingtech.com/php/gzip-compression.php
This example utilises the PHP buffer correctely and manual takes the buffered content, sends the compress data headers relevant to the compression method, then it compresses the blob before returning it to the buffer. The buffers is then dispaly or returns to the browser as compress content. I dont think this method is dependant on having Apache mod_gzip installed for the auto detection of the compression headers from the browser or to send the correct output headers.
This example has some issues an seems dated to use for compression. I think PHP has moved on. I would not use print as echo is quicker and I have no idea what print("\x1f\x8b\x08\x00\x00\x00\x00\x00"); does.
// Include this function on your pages function print_gzipped_page() { global $HTTP_ACCEPT_ENCODING; if( headers_sent() ){ $encoding = false; }elseif( strpos($HTTP_ACCEPT_ENCODING, 'x-gzip') !== false ){ $encoding = 'x-gzip'; }elseif( strpos($HTTP_ACCEPT_ENCODING,'gzip') !== false ){ $encoding = 'gzip'; }else{ $encoding = false; } if( $encoding ){ $contents = ob_get_contents(); ob_end_clean(); header('Content-Encoding: '.$encoding); print("\x1f\x8b\x08\x00\x00\x00\x00\x00"); $size = strlen($contents); $contents = gzcompress($contents, 9); $contents = substr($contents, 0, $size); print($contents); exit(); }else{ ob_end_flush(); exit(); } } // At the beginning of each page call these two functions ob_start(); ob_implicit_flush(0); // Then do everything you want to do on the page echo 'Hello World'; // Call this function to output everything as gzipped content. print_gzipped_page();
Links
- How to enable gzip compression with Php - Excellent article, I got example 1 from it.
- Use GZip compression on your PHP files · GitHub - An example script. This probably is in a legacy format.
zlib.output_compression = on - (automatic and invisible)
This is the prefered method for gzipping over ob_gzhandler()
The zlib extension can be used to transparently compress PHP pages on-the-fly if the browser sends an “Accept-Encoding: gzip” or “deflate” header. Compression with zlib.output_compression seems to be disabled on most hosts by default, but can be enabled with a custom php.ini file:
The zlib extension is the undelying technology and the zlib.output_compression = On is a switch that enables transparent/invisible compression on all PHP content. The zlib extension is also the library that is used for other compressions such as ob_gzhandler. I would need to check which compressions operations were covered by it.
This method is installed on most servers but left of by default. It will automatically send the output back to the client's browser in a compressed form if the 'allow compressed content' header is sent with the page request. If enabled in the php.ini then no further coding is required in any php (or other) script, or for that matter any assets.
Enable via php.ini
Add or alter the following line in the php.ini
zlib.output_compression = On
Enable via a PHP script
If zlib.output_compresssion is disabled but installed, you can enable either by editing the php.ini (as above) or you can add the following in to your PHP script. This will only enable compression for the script it is included in. and you must add this code before any headers or output is sent from the script. You should also note that having this method of compression will cause errors if further compression methods are implemented in your scripts.
if (extension_loaded("zlib") && (ini_get("output_handler") != "ob_gzhandler")) { @ob_end_clean(); @ini_set("zlib.output_compression", 1); }
As you can see this simple script makes a few checks, that the libary is present and that ob_gzhandler() has not been set. This is perhaps optional depending on your script, the ob_end_clean() just makes sure any buffers are emptied, I am not sure this is needed either. The @ symbol just surpresses errors and again these could be removed
Links
gzencode / gzcompress / gzdeflate - (for a single blob)
There is a difference between these 3 functions even thought they all compress a blob you supply to them. However the concesous is to use gzencode() as it outputs in the correct format including the required checksums. This function is also supports both gzip and deflate compression algorithyms.
Joomla Example
I have included this to see how these guys do it as they have more experience than me. This process happens in 2 distinct sections, the first check to see if the gzip function is enabled whilst check to see if the server supports zlib compression and that ob_gzhandler has not already been set.
/** * Execute the application. * * @return void * * @since 3.2 * * From {Joomla}libraries/cms/application/cms.php */ /** * @package Joomla.Libraries * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ public function execute() { // Perform application routines. $this->doExecute(); // If we have an application document object, render it. if ($this->document instanceof JDocument) { // Render the application output. $this->render(); } // If gzip compression is enabled in configuration and the server is compliant, compress the output. if ($this->get('gzip') && !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler')) { $this->compress(); // Trigger the onAfterCompress event. $this->triggerEvent('onAfterCompress'); } // Send the application response. $this->respond(); // Trigger the onAfterRespond event. $this->triggerEvent('onAfterRespond'); }
The second section actually does the compression and further checking, and yes there is some duplication of these checks. This fucntion supports the use of gzip or deflate compression algorithyms which is great for compatability. If you look at the code you can see that gzencode() is used and that if performs compression on a single variable (blob) rather than a page. gzencode() is able to use both algorithyms where as the other 2 functions this section covers cannot.
The code has been compress but because of the nature of the function you have to manually send the 'Content-Encoding' header so the browser knows the payload is compressed and how it has been compressed.
/** * Checks the accept encoding of the browser and compresses the data before * sending it to the client if possible. * * @return void * * @since 11.3 * * From {Joomla}libraries/joomla/application/web.php */ /** * @package Joomla.Platform * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ protected function compress() { // Supported compression encodings. $supported = array( 'x-gzip' => 'gz', 'gzip' => 'gz', 'deflate' => 'deflate' ); // Get the supported encoding. $encodings = array_intersect($this->client->encodings, array_keys($supported)); // If no supported encoding is detected do nothing and return. if (empty($encodings)) { return; } // Verify that headers have not yet been sent, and that our connection is still alive. if ($this->checkHeadersSent() || !$this->checkConnectionAlive()) { return; } // Iterate through the encodings and attempt to compress the data using any found supported encodings. foreach ($encodings as $encoding) { if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) { // Verify that the server supports gzip compression before we attempt to gzip encode the data. // @codeCoverageIgnoreStart if (!extension_loaded('zlib') || ini_get('zlib.output_compression')) { continue; } // @codeCoverageIgnoreEnd // Attempt to gzip encode the data with an optimal level 4. $data = $this->getBody(); $gzdata = gzencode($data, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE); // If there was a problem encoding the data just try the next encoding scheme. // @codeCoverageIgnoreStart if ($gzdata === false) { continue; } // @codeCoverageIgnoreEnd // Set the encoding headers. $this->setHeader('Content-Encoding', $encoding); // Header will be removed at 4.0 if ($this->get('MetaVersion')) { $this->setHeader('X-Content-Encoded-By', 'Joomla'); } // Replace the output with the encoded data. $this->setBody($gzdata); // Compression complete, let's break out of the loop. break; } } }
So that is how Joomla does compression and it should be noted that this method is probably the most compatible way of doing compression.
Links
- PHP Compression: gzcompress vs gzdeflate vs gzencode - This article goes in to what the differences are between these 3 blob compression functions and which one to use and why, for reference is gzencode
- Which compression method to use in PHP? - Stack Overflow - Again this gice information about the 3 different functions and which one to use and why.
- PHP: gzencode - Manual
What I used in QWcrm
I wrote my own function bas heavily of the Joomla function to enable gzip. In QWcrm I was able to do this becaus emy page is stored in a single varible/blob that I can manipulate.
In the main index.php I have the following code
################################################ # Page Compression # ################################################ // Compress page and send correct compression headers if ($gzip == true && $VAR['theme'] !== 'print') { $BuildPage = compress_page_output($BuildPage); }
This is the function code stored in include.php
########################################### # Compress page output and send headers # ########################################### /** * Checks the accept encoding of the browser and compresses the data before * sending it to the client if possible. * * @return void * * @since 11.3 * * From {Joomla}libraries/joomla/application/web.php */ /** * @package Joomla.Platform * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @copyright Copyright (C) 2017 - Jon Brown / Quantumwarp.com * @license GNU General Public License version 2 or later; see LICENSE */ function compress_page_output($BuildPage) { // Supported compression encodings. $supported = array( 'x-gzip' => 'gz', 'gzip' => 'gz', 'deflate' => 'deflate' ); // Get the supported encoding. $encodings = array_intersect(browserSupportedCompressionEncodings(), array_keys($supported)); // If no supported encoding is detected do nothing and return. if (empty($encodings)) { return $BuildPage; } // Verify that headers have not yet been sent, and that our connection is still alive. if (headers_sent() || (connection_status() !== CONNECTION_NORMAL)) { return $BuildPage; } // Iterate through the encodings and attempt to compress the data using any found supported encodings. foreach ($encodings as $encoding) { if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) { // Verify that the server supports gzip compression before we attempt to gzip encode the data. if (!extension_loaded('zlib') || ini_get('zlib.output_compression')) { continue; } // Attempt to gzip encode the page with an optimal level 4. $gzBuildPage = gzencode($BuildPage, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE); // If there was a problem encoding the data just try the next encoding scheme. if ($gzBuildPage === false) { continue; } // Set the encoding headers. header("Content-Encoding: $encoding"); // Replace the output with the encoded data. return $gzBuildPage; } } } #################################################################### # Get the supported compression algorithms in the client browser # #################################################################### function browserSupportedCompressionEncodings() { return array_map('trim', (array) explode(',', $_SERVER['HTTP_ACCEPT_ENCODING'])); }
In future I will try and see if the mod_deflate option is working on other servers as this might be built into newer version of the module to treat PHP output as normal files and compress them.
Other Links
- output buffering - What's the use of ob_start() in php? - Stack Overflow - A thread explaining what it is used for, which is not just compression.
- How To Optimize Your Site With GZIP Compression – BetterExplained - This covers the use of gzip in general, the protocol and sending of headers to then receiving back compressed assets. It does not cover PHP compression.
- Enabling Gzip Compression of PHP, CSS, and JS Files Without mod_deflate - WarpConduit Computing - An intersting article on compressing assets without using mod_deflate by using an intermediatery PHP file and .htaccess rewrite rules. I can say I would ever use this but there might be situations it could be useful.
- Speed Up your Website with Gzip Compression - An overview of gzip and real world usage. This is useful for noobies who want a little bit more information.
- How to compress .php, .css and .js files without mod_gzip or mod_deflate | Ardamis - This article has some good ideas and explanations but perhaps would not be the way I did compression.
- Enabling Gzip Compression of PHP, CSS, and JS Files Without mod_deflate - WarpConduit Computing - If you really need to do this.