Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any good Zend Framework + Minify implementations?

Are there any good implementations of Minify integration with Zend Framework? I'm looking for examples.

I'd love to have a plugin that overrides $this->headLink() and spits out the correct minified url/content.

Edit:

It seems most examples I find aren't fully optimized in one form or fashion. I'm looking for a solution that meets the following requirements:

Reduces multiple links and script tags to one request (one for link and one for scripts) The closest I've seen is a request path that passes a comma-delimited string to /min/ like so:

<script src="/min?f=file1.js,file2,js,file3.js" type="text/javascript"></script>

Why not something that combines all scripts into one file on disk on the fly and then caches it so that you aren't doing the minification on every request?

<script src="/js/app.js?someRandomStringHere" type="text/javascript"></script>

The combining aspect should maintain order (in reference to prepend, append, etc)

While I don't care so much about sending correct expires headers because I force gzipping, etags, and expires headers on the server-side, having that optional would be beneficial to other users.

Lastly, having a build script that generates the minified assets isn't necessary bad - as long as it is easy to do and doesn't require a code change after every build.

like image 810
doremi Avatar asked Jan 06 '11 19:01

doremi


3 Answers

This is what I use, the class is shown followed by use cases. I've commented it quickly, some of it might need changing to match your paths or define() the PUBLICPATH

class View_Helper_Minify extends Zend_View_Helper_Abstract
{
    public function minify($files, $ext, $folderName)
    {   
        // The folder of the files your about to minify
        // PUBLICPATH should be the path to your public ZF folder
        $folder = PUBLICPATH . $folderName . "/";

        // Set update needed flag to false
        $update_needed = false;

        // This is the file ext of the cached files
        $cacheFileExt = "." . $ext;

        // The list of files sent is exploded into an array
        $filesExploded = explode(',', $files);

        // The full cached file path is an md5 of the files string
        $cacheFilePath = $folder . md5($files) . $cacheFileExt;

        // The filename of the cached file 
        $cacheFileName = preg_replace("#[^a-zA-Z0-9\.]#", "", end(explode("/", $cacheFilePath)));

        // Obtains the modified time of the cache file
        $cacheFileDate = is_file($cacheFilePath) ? filemtime($cacheFilePath) : 0;

        // Create new array for storing the list of valid files
        $fileList = array();

        // For each file
        foreach($filesExploded as $f)
        {
            // determine full path of the full and append extension
            $f = $folder . $f . '.' . $ext;

            // If the determined path is a file
            if(is_file($f))
            {
                // If the file's modified time is after the cached file's modified time
                // Then an update of the cached file is needed
                if(filemtime($f) > $cacheFileDate)
                    $update_needed = true;

                // File is valid add to list 
                $fileList[] = $f;
            }
        }

        // If the cache folder's modified time is after the cached file's modified time
        // Then an update is needed
        if(filemtime($folder) > $cacheFileDate) 
            $update_needed = true;

        // If an update is needed then optmise the valid files
        if($update_needed)
            $this->optmiseFiles($fileList, $cacheFilePath, $ext);

        // Finally check if the cached file path is valid and return the absolute URL
        // for te cached file
        if(is_file($cacheFilePath))
            return "/" . $folderName . "/" . $cacheFileName;

        // Throw Exception
        throw new Exception("No minified file cached");             
    }

    private function optimise($code, $ext)
    {
        // Do not optmise JS files
        // I had problems getting JS files optmised and still function
        if($ext == "js")
            return $code;

        // Remove comments from CSS
        while(($i = strpos($code, '/*')) !== false)
        {
            $i2 = strpos($code, '*/',$i);

            if($i2 === false) 
                break;

            $code = substr($code, 0, $i).substr($code, $i2 + 2);
        }

        // Remove other elements from CSS
        $code = str_replace('/*','',$code);
        $code = str_replace("\n",' ',$code);
        $code = str_replace("\r",' ',$code);
        $code = str_replace("\t",' ',$code);
        $code = @ereg_replace('[ ]+',' ',$code);
        $code = str_replace(': ',':', $code);
        $code = str_replace('; ',';', $code);
        $code = str_replace(', ',',', $code);
        $code = str_replace(' :',':', $code);
        $code = str_replace(' ;',';', $code);
        $code = str_replace(' ,',',', $code);

        // Return optimised code
        return $code;
    }

    // Optmise the list of files
    private function optmiseFiles($fileList, $cacheFilePath, $ext)
    {
        // Empty String to start with
        $code = '';

        // Check files list in case just one file was passed
        if(is_array($fileList))
        {
            // Foreach of the valid files optmise the code if the file is valid
            foreach($fileList as $f)
                $code .= is_file($f) ? $this->optimise(implode('', file($f)), $ext) : '';
        }
        // Else if a valid file is passed optmise the code
        else
            $code = is_file($fileList) ? $this->optimise(implode('', file($fileList)), $ext) : '';

        // Open the cache file
        $f = @fopen($cacheFilePath, 'w');

        // If open successful
        if(is_resource($f))
        {
            // Write code to the cache file
            fwrite($f, $code);

            // close cache file
            fclose($f);
        }
    }   
}

You would use the helper like this in your view

// Define an array of files, note you do not define the ext of those files
// The ext is defined as a param for the helper as this effects the optmisation  
$files = array("jquery-ui-1.8.7.custom",
            "jquery.pnotify.default",
            "jquery.pnotify.default.icons",
            "tipTip",
            "prettyPhoto",
            "custom");

// Get the absolute URL of the files which are imploded also pass the directory 'css' and ext 'css' 
$cssString = $this->minify(implode("," , $files), "css", "css");

// use baseURL() to output the full URL of the cached file and use it as normal with headLink()
echo $this->headLink()
->appendStylesheet($this->baseUrl($cssString));

And here is a javascript version

$files = array("jquery-1.4.4.min",
            "jquery.pnotify.min",
            "jquery.tipTip.minified",
            "jquery.countdown.min",
            "jquery.prettyPhoto",
            "jquery.typewatch",
            "default.functions");

$jsString = $this->minify(implode("," , $files), "js", "scripts");

echo $this->headScript()->appendFile($this->baseUrl($jsString));
like image 182
Jake N Avatar answered Oct 31 '22 08:10

Jake N


I am trying to do the same thing right now. I am looking at NC State University's OT Framework, based on Zend Framework. This is implemented as a view helper. It has a nice class to minify all headscripts and headlinks via the Minify on Google Code:

http://ot.ncsu.edu/2010/03/03/getting-started-with-ot-framework/

Headscripts:

<?php

/**
 * Minifies the javascript files added via the minifyHeadScript helper using 
 * minify (http://code.google.com/p/minify/)
 *
 */
class Ot_View_Helper_MinifyHeadScript extends Zend_View_Helper_HeadScript
{

    protected $_regKey = 'Ot_View_Helper_MinifyHeadScript';

    public function minifyHeadScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
    {
        return parent::headScript($mode, $spec, $placement, $attrs, $type);
    }

    public function toString()
    {
        $items = array();
        $scripts = array();
        $baseUrl = $this->getBaseUrl();

        // we can only support files
        foreach ($this as $item) {
            if (isset($item->attributes['src']) && !empty($item->attributes['src'])) {
                $scripts[] = str_replace($baseUrl, '', $item->attributes['src']);
            }
        }

        //remove the slash at the beginning if there is one
        if (substr($baseUrl, 0, 1) == '/') {
            $baseUrl = substr($baseUrl, 1);
        }

        $item = new stdClass();
        $item->type = 'text/javascript';
        $item->attributes['src'] = $this->getMinUrl() . '?b=' . $baseUrl . '&f=' . implode(',', $scripts);
        $scriptTag = $this->itemToString($item, '', '', '');

        return $scriptTag;
    }

    public function getMinUrl() {
        return $this->getBaseUrl() . '/min/';
    }

    public function getBaseUrl(){
        return Zend_Controller_Front::getInstance()->getBaseUrl();
    }
}

And here is the code for headlinks:

<?php

/**
 * Minifies the stylesheets added via the minifyHeadLink helper using 
 * minify (http://code.google.com/p/minify/)
 *
 */
class Ot_View_Helper_MinifyHeadLink extends Zend_View_Helper_HeadLink
{

    protected $_regKey = 'Ot_View_Helper_MinifyHeadLink';

    public function minifyHeadLink(array $attributes = null, $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
    {
        return parent::headlink($attributes, $placement);
    }

    public function toString()
        {
        $items = array();
        $stylesheets = array();
        $baseUrl = $this->getBaseUrl();

        foreach ($this as $item) {
            if ($item->type == 'text/css' && $item->conditionalStylesheet === false) {
                $stylesheets[$item->media][] = str_replace($baseUrl, '', $item->href);
            } else {
                $items[] = $this->itemToString($item);
            }
        }

        //remove the slash at the beginning if there is one
        if (substr($baseUrl, 0, 1) == '/') {
            $baseUrl = substr($baseUrl, 1);
        }

        foreach ($stylesheets as $media=>$styles) {
            $item = new stdClass();
            $item->rel = 'stylesheet';
            $item->type = 'text/css';
            $item->href = $this->getMinUrl() . '?b=' . $baseUrl . '&f=' . implode(',', $styles);
            $item->media = $media;
            $item->conditionalStylesheet = false;
            $items[] = $this->itemToString($item);
        }

        $link = implode($this->_escape($this->getSeparator()), $items);

        return $link;
    }

    public function getMinUrl() {
        return $this->getBaseUrl() . '/min/';
    }

    public function getBaseUrl(){
        return Zend_Controller_Front::getInstance()->getBaseUrl();
    }
}
like image 32
cjroth Avatar answered Oct 31 '22 10:10

cjroth


I ran across the same problem and ended up writing two drop-in helpers to manage it for me. You can see them at http://blog.hines57.com/2011/03/13/zendframework-minify/ - thanks again. Quick overview for one of them:

 * * ** PREREQUISITES **
 * This file expects that you have installed minify in ../ZendFramworkProject/Public/min 
 * and that it is working. If your location has changed, modify 
 * $this->$_minifyLocation to your current location.
 * 
 * ** INSTALLATION **
 * Simply drop this file into your ../ZendFramworkProject/application/views/helpers
 * directory.
 * 
 * ** USAGE **
 * In your Layout or View scripts, you can simply call minifyHeadLink
 * in the same way that you used to call headLink. Here is an example:
 * 
  echo $this->minifyHeadLink('/favicon.ico')             // Whatever was already loaded from Controller.
  ->prependStylesheet('http://example.com/js/sample.css')// 6th
  ->prependStylesheet('/js/jqModal.css')                 // 5th
  ->prependStylesheet('/js/jquery.alerts.css')           // 4th
  ->prependStylesheet('/templates/main.css')             // 3rd
  ->prependStylesheet('/css/project.css.php')            // 2nd
  ->prependStylesheet('/css/jquery.autocomplete.css')    // 1st
  ->appendStylesheet('/css/ie6.css','screen','lt IE 7'); // APPEND to make it Last
 *
 * 
 * This can be interesting because you will notice that 2nd is a php file, and we
 * have a reference to a favicon link in there as well as a reference to a css file on
 * another website. Because minify can't do anything with that php file (runtime configured 
 * css file) nor with CSS on other websites, and order is important,you would notice that 
 * the output in your browser will looks something like:
 * 
   <link href="/min/?f=/css/jquery.autocomplete.css" media="screen" rel="stylesheet" type="text/css" />
   <link href="/css/project.css.php" media="screen" rel="stylesheet" type="text/css" />
   <link href="/min/?f=/templates/main.css,/js/jquery.alerts.css,/js/jqModal.css" media="screen" 
               rel="stylesheet" type="text/css" />
   <link href="http://example.com/js/sample.css" media="screen" rel="stylesheet" type="text/css" />
   <link href="/favicon.ico" rel="shortcut icon" />
   <!--[if lt IE 7]> <link href="/css/ie6.css" media="screen" rel="stylesheet" type="text/css" /><![endif]-->
like image 34
bubba Avatar answered Oct 31 '22 10:10

bubba