Last updated: October 9, 2015 (at 2am XD)

If I remember correctly, in the version 1.3 of Cake you could find a css.php file in the webroot folder. Using ccsspp, cake could compress the css files of your website.

However, I’ve just downloaded a copy of CakePHP v2.0.6Stable and to my surprise I’ve found that it doesn’t include the css.php file anymore. So I tried to use the old css.php found in the older versions and (obviously) it didn’t worked… anyway, the code of the css.php file wasn’t quite elegant, so I’ve decided to rewrite a new file.

Well, actually I could find the csspp Vendor mentioned in the core file (ln 211). I guess it was this compressor found on github… But i wanted to use a different compressor, like the YUIcompressor. So I kinda separated the compressor with the css.php file.

The git repository of this code is here: https://github.com/pleasedontbelong/cakephp2.x-static-compressor

Download and install a compressor

  • Download csspp.php and put it into the vendors folder (you should have a app/Vendor/csspp/csspp.php file)
  • Or if you want to use the YUICompressor you'll need to Download yuicompressor and unzip it in the vendors folder (you should have the jar file placed in app/Vendor/yuicompressor/build/yuicompressor-2.4.7.jar). If you need to change the version or the location of the file you'll need to change the Compressor.php file too (see the code below). Of course you MUST have java installed on the machine if you want to use the YUICompressor.

Configure CakePhp

  • Change your “core.php” (inside the Config folder) file and add this line: Configure::write('Asset.filter.css', 'mini.php'); (optionally, uncomment the Asset.filter.js and change it to mini.php)

  • Create the app/tmp/cache/css and the app/tmp/cache/js folder, both folder must be writable like the other tmp folders
  • Create the file mini.php in the app/webroot folder. This file will check if there’s already the file compressed in cache and call the Compressor class to compress the code:
    
    /**
    * JS / CSS minifier
    *
    * PHP versions 4 and 5
    *
    * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
    * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
    *
    * @author Pablo Leano
    */
    App::uses('File', 'Utility');
    class Mini {
      
      const CSS = "css";
      const JS = "js";
      
      private $response;
      private $filename;
      private $path;
      private $type;
      
      public function __construct($url,CakeResponse $response,$isCss = false,$isJs = false) {
        $this->url = $url;
        $this->response = $response;
        //get the type of file (js or css)
        if($isCss){
          $this->type = Mini::CSS;
          $this->path = CSS;
        }
        if($isJs){
          $this->type = Mini::JS;
          $this->path = JS;
        }
        if(!$this->type){
          throw new Exception("Incorrect type of file");
        }
        
        //extract the filename from the url
        if (preg_match('|\.\.|', $url) || !preg_match('#^(ccss|cjs)/(.+)$#i', $url, $matches)) {
          throw new Exception('Wrong file name');
        }
        $this->filename = $matches[2];
      }
      
      public function process(){
        
        //checks if the file exist in the webroot/css , or if it's in cache (case of merged files)
        // if the file is found on cache, changes the $this->cachename
        $this->_validates();
        
        $cachefile = CACHE . $this->type . DS . str_replace(array('/','\\'), '-', $this->filename);
        
        //the the compressed file either from cache or from using a compressor
        if (file_exists($cachefile)) {
          $templateModified = filemtime($this->path . $this->filename);
          $cacheModified = filemtime($cachefile);
          
          //if the file is more recent than the cache, compress it
          if ($templateModified > $cacheModified) {
            $output = $this->_getCompressed();
            $this->_writeToCache($cachefile, $output);
          } else {
            $output = file_get_contents($cachefile);
          }
        } else {
          $output = $this->_getCompressed();
          $this->_writeToCache($cachefile, $output);
          $templateModified = time();
        }
        //create the response body and headers
        $this->response->header('Date',date("D, j M Y G:i:s ", $templateModified) . 'GMT');
        //set the correct content type
        //$this->response->header('Content-Type',($this->type == Mini::CSS ? "text/css" : "application/x-javascript"));
        $this->response->type(($this->type == Mini::CSS ? "text/css" : "application/x-javascript"));
        $this->response->header('Expires', gmdate("D, d M Y H:i:s", time() + DAY) . " GMT");
        $this->response->header('Cache-Control','max-age=86400, must-revalidate'); // HTTP/1.1
        $this->response->header('Pragma: cache');        // HTTP/1.0
        $this->response->body($output);
        $this->response->send();
      }
      
      /**
       * Gets the compressed content of the current file
       */
      private function _getCompressed(){
        App::import('Vendor', 'Compressor');
        // here i'm using csspp but you could also use Yuicompressor
        $compressor = new Compressor($this->filename, $this->path, $this->type);
        return $compressor->process();
      }
      
      /**
       * Write the content to a file
       * @param string $cachefile path for the cache file
       * @param string $content file's content
       * @throws Exception
       */
      private function _writeToCache($cachefile, $content){
        $cache = new File($cachefile);
        if(!$cache->write($content)){
          throw new Exception('Could not write cache file');
        } 
      }
      
      private function _validates(){
        if (($this->type == Mini::CSS && !file_exists(CSS . $this->filename)) || ($this->type == Mini::JS && !file_exists(JS . $this->filename))) {
          //check file exists on cache
          if(!file_exists(CACHE . $this->type . DS . $this->filename)){
            throw new Exception('File not found');
          }
        }
      }
      
    }
    
    $mini = new Mini($url,$response,$isCss,$isJs);
    try {
      $mini->process();
    } catch (Exception $e) {
      exit($e->getMessage());
    }
    
  • Finally create the Compressor.php file in the vendors/ folder. This file will select the defined compressor (yuicompressor, csspp , or any other library that you want):
    
    /**
     * compresses the file using the selected library
     *  
     * @author Juan Pablo Leano
     *
     */
    class Compressor {
      
      /**
       * The compressor (library) to be used
       */
      private $vendor;
      private $file;
      private $path;
      
      function __construct($file,$path,$type,$vendor = 'yuicompressor') {
        $this->file = $file;
        $this->path = $path;
        $this->vendor = $vendor;
        $this->type = $type;
      }
      
      public function process(){
        switch ($this->vendor) {
          case 'yuicompressor':
            exec('java -jar ' . APP . 'Vendor/yuicompressor/build/yuicompressor-2.4.7.jar --line-break 8000 --type ' . $this->type . " " . $this->path . $this->file , $output, $return);
            if($return != 0){
              throw new Exception("Yuicompressor could not compress the file");
            } 
            return implode("\n", $output);
            break;
          case 'csspp':
            if($this->type == "css"){
              App::import('Vendor', 'csspp' . DS . 'csspp');
              $filename = $this->path . $this->file;
              $data = file_get_contents($filename);
              $csspp = new csspp($filename,'');
              $output = $csspp->process();
              $ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100);
              $output = " /* file: " . $this->file . ", ratio: $ratio% */ \n " . $output;
              return $output;
            } else {
              // csspp can only compress css files
              return file_get_contents($this->path . $this->file);
            }
            break;
          default:
            throw new Exception("Compressor not found");
          break;
        }
      }
    }
    

and that’s it… I’ve tested it at the same time that I’m writing this post, and it’s working like a charm =)

If you have any problems using this code, feel free to write an issue on github, or if you want to improve it you could write an make a pull request. I’ve created this class rapidly so it’s not perfect

Update: I've created a docker image to test if this code is still workin, and it does. I used CakePHP 2.7.5 (I dont't have time to learn cakePHP 3 since I no longer write PHP code). You can find a Live Demo but there's not too much to see there, just the default cakephp page however if you inspect the page you'll see that the css is being compressed by the YUICompressor

If you want to install the docker image, you'll find all the code inside the /code folder in the container