In my previous post, I talked about using YUI for dependency management and what’s needed for the client-side. It involved performing the URL fetch of concatenated files and serving them from a single HTTP request.

In this article I’m going to talk about how to hook up that missing piece – the server-side component that takes cares of concatenating, minifying and gzipping your files.

In a recent Google search, I wanted to look into what already existed out there for serving minified JavaScript and CSS files. The first article that popped up was Minify Helper for CakePHP. I gave it a quick read and decided I would probably want to use it. Cool.

After getting it all setup and placing the min directory in the webroot (as the article says to do), I quickly realized that I don’t have access to any CakePHP constants like WWW_ROOT or any CakePHP configuration settings. Boooooo.

I decided to do the following:

  • Move the min directory out of the webroot directory and into app/vendors
  • Set up a controller called MinifyController to handle any minification requests
  • Including PHP Minify (app/vendors/min) as a vendor in my MinifyController
  • Set up routing to route any requests from /min-js?f= and /min-css?f= to my MinifyController
  • Still use the helper from this article, but slightly modified

Here is my step-by-step process to get it all setup.

Setup

Download PHP Minify. There should be two folders, one called min and one called min-unit-tests – the min folder is what we want. Copy the min directory to your app/vendors folder. So it should now look like app/vendors/min.

Create the controller

Go to your app/controllers directory and create a file called minify_controller.php. Now drop this code into it:

<?
class MinifyController extends AppController {
 
    public $name = 'Minify';
    public $uses = array();
 
    /**
     * In this case, in 7shifts our AppController::beforeFilter() takes advantage of the $Auth component. 
     * We don't want to use it here so we just over-write the beforeFilter() method with nothing. 
     */
    public function beforeFilter(){}
 
    /**
     * Take care of any minifying requests. 
     *
     * @access public
     */
    public function index(){
 
        $this->autoRender = false;
        // We define this to then use in the config.php for PHP Minify
        define('WEBROOT_URL', $this->webroot);
        App::import('Vendor', 'min/index');
 
    }
 
}
?>

Now it’s time to set some configurations.

Configuring PHP Minify

Open up app/vendors/min/config.php. Here is where we’ll need to do some tweaking. With PHP Minify, it stores cached copies of the gzipped files. We need to tell it where to store these temporary files. Oh what? You say CakePHP has a tmp directory? Why yes! That is where we’ll store these temporary files.

Create a directory in app/tmp called minify. So it should look like app/tmp/minify. Now hop back to your config.php file and find this commented-out line:

//$min_cachePath = 'c:\\WINDOWS\\Temp';

Right above that, add this:

// Using the TMP constant to give us the path of our tmp dir (automatically defined by our CakePHP app)
$min_cachePath = TMP . '/minify';

Now find the line:

$min_documentRoot = '';

And replace it with

// WWW_ROOT is automatically defined by our CakePHP app
$min_documentRoot = WWW_ROOT;

When our minifier concatenates our CSS files, it often breaks the path to images. There is an option in PHP Minify that you can set to have it re-write your image paths to the proper directory. Now this will probably different for you, so you’ll have to modify it to use the appropriate path for your image assets.

$min_serveOptions['rewriteCssUris'] = false;
// WEBROOT_URL is defined by us in minify_controller.php
$min_serveOptions['minifierOptions']['text/css']['prependRelativePath'] = WEBROOT_URL . 'css/theme/';

The rest of the options in this config.php file can be modified to suit the needs of your own app.

Routing

Now it’s time to make sure that any request made to http://yourapp.com/min-js and http://yourapp.com/min-css get routed to the Minify controller. To do this we open up app/config/routes.php. Add these two lines to it:

// These need to have -js and -css post-fixes because of YUI. In the YUI loader setup, it realizes that
// there are two different URL's, one for js modules, one for css modules.
// It will call the appropriate url depending on what assets we're calling in our YUI Loader. 
// If we only had one URL, YUI would try to concatenate JS and CSS files into one HTTP request which would
// break everything :(
Router::connect('/min-js', array('controller' => 'minify', 'action' => 'index'));
Router::connect('/min-css', array('controller' => 'minify', 'action' => 'index'));

All of the work you see above is to have PHP Minify work together with YUI Loader. If you don’t understand what YUI Loader has to do with any of this, you probably didn’t read my first article – shame on you.

Now what if we want to include minified JS and CSS from a helper (in one of the views) and have it take advantage of our PHP Minify vendor? Easy. I took the helper from here and modified it a bit.

In your app/views/helper directory, create a file called minify.php. Now include this code in it:

<?php
/***
 * Cakephp view helper to interface with http://code.google.com/p/minify/ project.
 * Minify: Combines, minifies, and caches JavaScript and CSS files on demand to speed up page loads.
 * @author: Ketan Shah - ketan.shah@gmail.com - http://www.innovatechnologies.in
 * Requirements: An entry in core.php - "MinifyAsset" - value of which is either set 'true' or 'false'. False would be usually set during development and/or debugging. True should be set in production mode.
 */
 
Class MinifyHelper extends AppHelper {
 
        var $helpers = array('Javascript', 'Html'); //used for seamless degradation when MinifyAsset is set to false;
 
        function js($assets){
            if(Configure::read('MinifyAsset')){
               e(sprintf("<script type='text/javascript' src='%s'></script>", $this->_path($assets, 'js')));
            }
            else{
                e($this->Html->script($assets));
            }
        }
 
 
        function css($assets){
            if(Configure::read('MinifyAsset')){
                e(sprintf("<link type='text/css' rel='stylesheet' href='%s' />",$this->_path($assets, 'css')));
            }
            else{
                e($this->Html->css($assets));
            }
        }
 
        function _path($assets, $ext){
            if(!is_array($assets)){
                $assets = array($assets);
            }
            $path = $this->webroot . "min-" . $ext . "?f=";
            foreach($assets as $asset){
                $path .= ($ext . '/' . $asset . ".$ext,");
            }
            return substr($path, 0, count($path)-2);
        }
    }
 
?>

Notice the

Configure::read('MinifyAsset')

in that helper file. It’s trying to read a configuration setting that we haven’t explicitly set yet. So let’s do that now.

Open up app/config/core.php and add this line:

Configure::write('MinifyAsset', true);

This configuration was done to allow better debugging when you’re working locally. This should always be true on production. If set to false, it will just call CakePHP’s internal way of including scripts/css ($Html->script and $Html->css) which is to just include every file uncompressed as an HTTP request.

Now it’s time to use our helper. In your controller, you’ll have a $helpers array at the top. Add ‘Minify’ to the helpers array.

Go into any view file and you should be able to call something like:

echo $minify->js('global');
// or
echo $minify->js(array('global', 'another-js-file', 'andanother'));

or

echo $minify->css('somecssfile');

Enjoy!