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.
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));
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();
}
}
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]-->
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With