1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-09 07:36:56 +02:00

Moved file caching to Minify_Cache_File.

Removed Cache_Lite.
Deprecated UseServerCache() in favor of setCache().
Added Groups controller to ab tests.
This commit is contained in:
Steve Clay
2008-06-22 15:08:30 +00:00
parent a6d7115a6c
commit 4f615c12f3
15 changed files with 201 additions and 1058 deletions

View File

@@ -1,925 +0,0 @@
<?php
// since Minify relies on a (slightly) patched version of Cache_Lite_File,
// I've included it here in a single include.
/**
* Fast, light and safe Cache Class
*
* Cache_Lite is a fast, light and safe cache system. It's optimized
* for file containers. It is fast and safe (because it uses file
* locking and/or anti-corruption tests).
*
* There are some examples in the 'docs/examples' file
* Technical choices are described in the 'docs/technical' file
*
* Memory Caching is from an original idea of
* Mike BENOIT <ipso@snappymail.ca>
*
* Nota : A chinese documentation (thanks to RainX <china_1982@163.com>) is
* available at :
* http://rainx.phpmore.com/manual/cache_lite.html
*
* @package Cache_Lite
* @category Caching
* @version $Id: Lite.php,v 1.45 2006/06/03 08:10:33 fab Exp $
* @author Fabien MARTY <fab@php.net>
*/
define('CACHE_LITE_ERROR_RETURN', 1);
define('CACHE_LITE_ERROR_DIE', 8);
class Cache_Lite
{
// --- Private properties ---
/**
* Directory where to put the cache files
* (make sure to add a trailing slash)
*
* @var string $_cacheDir
*/
var $_cacheDir = '/tmp/';
/**
* Enable / disable caching
*
* (can be very usefull for the debug of cached scripts)
*
* @var boolean $_caching
*/
var $_caching = true;
/**
* Cache lifetime (in seconds)
*
* If null, the cache is valid forever.
*
* @var int $_lifeTime
*/
var $_lifeTime = 3600;
/**
* Enable / disable fileLocking
*
* (can avoid cache corruption under bad circumstances)
*
* @var boolean $_fileLocking
*/
var $_fileLocking = true;
/**
* Timestamp of the last valid cache
*
* @var int $_refreshTime
*/
var $_refreshTime;
/**
* File name (with path)
*
* @var string $_file
*/
var $_file;
/**
* File name (without path)
*
* @var string $_fileName
*/
var $_fileName;
/**
* Enable / disable write control (the cache is read just after writing to detect corrupt entries)
*
* Enable write control will lightly slow the cache writing but not the cache reading
* Write control can detect some corrupt cache files but maybe it's not a perfect control
*
* @var boolean $_writeControl
*/
var $_writeControl = true;
/**
* Enable / disable read control
*
* If enabled, a control key is embeded in cache file and this key is compared with the one
* calculated after the reading.
*
* @var boolean $_writeControl
*/
var $_readControl = true;
/**
* Type of read control (only if read control is enabled)
*
* Available values are :
* 'md5' for a md5 hash control (best but slowest)
* 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
* 'strlen' for a length only test (fastest)
*
* @var boolean $_readControlType
*/
var $_readControlType = 'crc32';
/**
* Pear error mode (when raiseError is called)
*
* (see PEAR doc)
*
* @see setToDebug()
* @var int $_pearErrorMode
*/
var $_pearErrorMode = CACHE_LITE_ERROR_RETURN;
/**
* Current cache id
*
* @var string $_id
*/
var $_id;
/**
* Current cache group
*
* @var string $_group
*/
var $_group;
/**
* Enable / Disable "Memory Caching"
*
* NB : There is no lifetime for memory caching !
*
* @var boolean $_memoryCaching
*/
var $_memoryCaching = false;
/**
* Enable / Disable "Only Memory Caching"
* (be carefull, memory caching is "beta quality")
*
* @var boolean $_onlyMemoryCaching
*/
var $_onlyMemoryCaching = false;
/**
* Memory caching array
*
* @var array $_memoryCachingArray
*/
var $_memoryCachingArray = array();
/**
* Memory caching counter
*
* @var int $memoryCachingCounter
*/
var $_memoryCachingCounter = 0;
/**
* Memory caching limit
*
* @var int $memoryCachingLimit
*/
var $_memoryCachingLimit = 1000;
/**
* File Name protection
*
* if set to true, you can use any cache id or group name
* if set to false, it can be faster but cache ids and group names
* will be used directly in cache file names so be carefull with
* special characters...
*
* @var boolean $fileNameProtection
*/
var $_fileNameProtection = true;
/**
* Enable / disable automatic serialization
*
* it can be used to save directly datas which aren't strings
* (but it's slower)
*
* @var boolean $_serialize
*/
var $_automaticSerialization = false;
/**
* Disable / Tune the automatic cleaning process
*
* The automatic cleaning process destroy too old (for the given life time)
* cache files when a new cache file is written.
* 0 => no automatic cache cleaning
* 1 => systematic cache cleaning
* x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
*
* @var int $_automaticCleaning
*/
var $_automaticCleaningFactor = 0;
/**
* Nested directory level
*
* Set the hashed directory structure level. 0 means "no hashed directory
* structure", 1 means "one level of directory", 2 means "two levels"...
* This option can speed up Cache_Lite only when you have many thousands of
* cache file. Only specific benchs can help you to choose the perfect value
* for you. Maybe, 1 or 2 is a good start.
*
* @var int $_hashedDirectoryLevel
*/
var $_hashedDirectoryLevel = 0;
/**
* Umask for hashed directory structure
*
* @var int $_hashedDirectoryUmask
*/
var $_hashedDirectoryUmask = 0700;
/**
* API break for error handling in CACHE_LITE_ERROR_RETURN mode
*
* In CACHE_LITE_ERROR_RETURN mode, error handling was not good because
* for example save() method always returned a boolean (a PEAR_Error object
* would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without
* breaking the API, this option (false by default) can change this handling.
*
* @var boolean
*/
var $_errorHandlingAPIBreak = false;
// --- Public methods ---
/**
* Constructor
*
* $options is an assoc. Available options are :
* $options = array(
* 'cacheDir' => directory where to put the cache files (string),
* 'caching' => enable / disable caching (boolean),
* 'lifeTime' => cache lifetime in seconds (int),
* 'fileLocking' => enable / disable fileLocking (boolean),
* 'writeControl' => enable / disable write control (boolean),
* 'readControl' => enable / disable read control (boolean),
* 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string),
* 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int),
* 'memoryCaching' => enable / disable memory caching (boolean),
* 'onlyMemoryCaching' => enable / disable only memory caching (boolean),
* 'memoryCachingLimit' => max nbr of records to store into memory caching (int),
* 'fileNameProtection' => enable / disable automatic file name protection (boolean),
* 'automaticSerialization' => enable / disable automatic serialization (boolean),
* 'automaticCleaningFactor' => distable / tune automatic cleaning process (int),
* 'hashedDirectoryLevel' => level of the hashed directory system (int),
* 'hashedDirectoryUmask' => umask for hashed directory structure (int),
* 'errorHandlingAPIBreak' => API break for better error handling ? (boolean)
* );
*
* @param array $options options
* @access public
*/
function Cache_Lite($options = array(NULL))
{
foreach($options as $key => $value) {
$this->setOption($key, $value);
}
}
/**
* Generic way to set a Cache_Lite option
*
* see Cache_Lite constructor for available options
*
* @var string $name name of the option
* @var mixed $value value of the option
* @access public
*/
function setOption($name, $value)
{
$availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode');
if (in_array($name, $availableOptions)) {
$property = '_'.$name;
$this->$property = $value;
}
}
/**
* Test if a cache is available and (if yes) return it
*
* @param string $id cache id
* @param string $group name of the cache group
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @return string data of the cache (else : false)
* @access public
*/
function get($id, $group = 'default', $doNotTestCacheValidity = false)
{
$this->_id = $id;
$this->_group = $group;
$data = false;
if ($this->_caching) {
$this->_setRefreshTime();
$this->_setFileName($id, $group);
clearstatcache();
if ($this->_memoryCaching) {
if (isset($this->_memoryCachingArray[$this->_file])) {
if ($this->_automaticSerialization) {
return unserialize($this->_memoryCachingArray[$this->_file]);
}
return $this->_memoryCachingArray[$this->_file];
}
if ($this->_onlyMemoryCaching) {
return false;
}
}
if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
if (file_exists($this->_file)) {
$data = $this->_read();
}
} else {
if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) {
$data = $this->_read();
}
}
if (($data) and ($this->_memoryCaching)) {
$this->_memoryCacheAdd($data);
}
if (($this->_automaticSerialization) and (is_string($data))) {
$data = unserialize($data);
}
return $data;
}
return false;
}
/**
* Save some data in a cache file
*
* @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
* @param string $id cache id
* @param string $group name of the cache group
* @return boolean true if no problem (else : false or a PEAR_Error object)
* @access public
*/
function save($data, $id = NULL, $group = 'default')
{
if ($this->_caching) {
if ($this->_automaticSerialization) {
$data = serialize($data);
}
if (isset($id)) {
$this->_setFileName($id, $group);
}
if ($this->_memoryCaching) {
$this->_memoryCacheAdd($data);
if ($this->_onlyMemoryCaching) {
return true;
}
}
if ($this->_automaticCleaningFactor>0) {
$rand = rand(1, $this->_automaticCleaningFactor);
if ($rand==1) {
$this->clean(false, 'old');
}
}
if ($this->_writeControl) {
$res = $this->_writeAndControl($data);
if (is_bool($res)) {
if ($res) {
return true;
}
// if $res if false, we need to invalidate the cache
@touch($this->_file, time() - 2*abs($this->_lifeTime));
return false;
}
} else {
$res = $this->_write($data);
}
if (is_object($res)) {
// $res is a PEAR_Error object
if (!($this->_errorHandlingAPIBreak)) {
return false; // we return false (old API)
}
}
return $res;
}
return false;
}
/**
* Remove a cache file
*
* @param string $id cache id
* @param string $group name of the cache group
* @return boolean true if no problem
* @access public
*/
function remove($id, $group = 'default')
{
$this->_setFileName($id, $group);
if ($this->_memoryCaching) {
if (isset($this->_memoryCachingArray[$this->_file])) {
unset($this->_memoryCachingArray[$this->_file]);
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
}
if ($this->_onlyMemoryCaching) {
return true;
}
}
return $this->_unlink($this->_file);
}
/**
* Clean the cache
*
* if no group is specified all cache files will be destroyed
* else only cache files of the specified group will be destroyed
*
* @param string $group name of the cache group
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
* 'callback_myFunction'
* @return boolean true if no problem
* @access public
*/
function clean($group = false, $mode = 'ingroup')
{
return $this->_cleanDir($this->_cacheDir, $group, $mode);
}
/**
* Set to debug mode
*
* When an error is found, the script will stop and the message will be displayed
* (in debug mode only).
*
* @access public
*/
function setToDebug()
{
$this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE);
}
/**
* Set a new life time
*
* @param int $newLifeTime new life time (in seconds)
* @access public
*/
function setLifeTime($newLifeTime)
{
$this->_lifeTime = $newLifeTime;
$this->_setRefreshTime();
}
/**
* Save the state of the caching memory array into a cache file cache
*
* @param string $id cache id
* @param string $group name of the cache group
* @access public
*/
function saveMemoryCachingState($id, $group = 'default')
{
if ($this->_caching) {
$array = array(
'counter' => $this->_memoryCachingCounter,
'array' => $this->_memoryCachingState
);
$data = serialize($array);
$this->save($data, $id, $group);
}
}
/**
* Load the state of the caching memory array from a given cache file cache
*
* @param string $id cache id
* @param string $group name of the cache group
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @access public
*/
function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false)
{
if ($this->_caching) {
if ($data = $this->get($id, $group, $doNotTestCacheValidity)) {
$array = unserialize($data);
$this->_memoryCachingCounter = $array['counter'];
$this->_memoryCachingArray = $array['array'];
}
}
}
/**
* Return the cache last modification time
*
* BE CAREFUL : THIS METHOD IS FOR HACKING ONLY !
*
* @return int last modification time
*/
function lastModified()
{
return @filemtime($this->_file);
}
/**
* Trigger a PEAR error
*
* To improve performances, the PEAR.php file is included dynamically.
* The file is so included only when an error is triggered. So, in most
* cases, the file isn't included and perfs are much better.
*
* @param string $msg error message
* @param int $code error code
* @access public
*/
function raiseError($msg, $code)
{
include_once('PEAR.php');
return PEAR::raiseError($msg, $code, $this->_pearErrorMode);
}
/**
* Extend the life of a valid cache file
*
* see http://pear.php.net/bugs/bug.php?id=6681
*
* @access public
*/
function extendLife()
{
@touch($this->_file);
}
// --- Private methods ---
/**
* Compute & set the refresh time
*
* @access private
*/
function _setRefreshTime()
{
if (is_null($this->_lifeTime)) {
$this->_refreshTime = null;
} else {
$this->_refreshTime = time() - $this->_lifeTime;
}
}
/**
* Remove a file
*
* @param string $file complete file path and name
* @return boolean true if no problem
* @access private
*/
function _unlink($file)
{
if (!@unlink($file)) {
return $this->raiseError('Cache_Lite : Unable to remove cache !', -3);
}
return true;
}
/**
* Recursive function for cleaning cache file in the given directory
*
* @param string $dir directory complete path (with a trailing slash)
* @param string $group name of the cache group
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
'callback_myFunction'
* @return boolean true if no problem
* @access private
*/
function _cleanDir($dir, $group = false, $mode = 'ingroup')
{
if ($this->_fileNameProtection) {
$motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
} else {
$motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
}
if ($this->_memoryCaching) {
while (list($key, ) = each($this->_memoryCachingArray)) {
if (strpos($key, $motif, 0)) {
unset($this->_memoryCachingArray[$key]);
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
}
}
if ($this->_onlyMemoryCaching) {
return true;
}
}
if (!($dh = opendir($dir))) {
return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
}
$result = true;
while ($file = readdir($dh)) {
if (($file != '.') && ($file != '..')) {
if (substr($file, 0, 6)=='cache_') {
$file2 = $dir . $file;
if (is_file($file2)) {
switch (substr($mode, 0, 9)) {
case 'old':
// files older than lifeTime get deleted from cache
if (!is_null($this->_lifeTime)) {
if ((mktime() - @filemtime($file2)) > $this->_lifeTime) {
$result = ($result and ($this->_unlink($file2)));
}
}
break;
case 'notingrou':
if (!strpos($file2, $motif, 0)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
case 'callback_':
$func = substr($mode, 9, strlen($mode) - 9);
if ($func($file2, $group)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
case 'ingroup':
default:
if (strpos($file2, $motif, 0)) {
$result = ($result and ($this->_unlink($file2)));
}
break;
}
}
if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
$result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
}
}
}
}
return $result;
}
/**
* Add some date in the memory caching array
*
* @param string $data data to cache
* @access private
*/
function _memoryCacheAdd($data)
{
$this->_memoryCachingArray[$this->_file] = $data;
if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) {
list($key, ) = each($this->_memoryCachingArray);
unset($this->_memoryCachingArray[$key]);
} else {
$this->_memoryCachingCounter = $this->_memoryCachingCounter + 1;
}
}
/**
* Make a file name (with path)
*
* @param string $id cache id
* @param string $group name of the group
* @access private
*/
function _setFileName($id, $group)
{
if ($this->_fileNameProtection) {
$suffix = 'cache_'.md5($group).'_'.md5($id);
} else {
$suffix = 'cache_'.$group.'_'.$id;
}
$root = $this->_cacheDir;
if ($this->_hashedDirectoryLevel>0) {
$hash = md5($suffix);
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
}
}
$this->_fileName = $suffix;
$this->_file = $root.$suffix;
}
/**
* Read the cache file and return the content
*
* @return string content of the cache file (else : false or a PEAR_Error object)
* @access private
*/
function _read()
{
$fp = @fopen($this->_file, "rb");
if ($this->_fileLocking) @flock($fp, LOCK_SH);
if ($fp) {
clearstatcache();
$length = @filesize($this->_file);
$mqr = get_magic_quotes_runtime();
set_magic_quotes_runtime(0);
if ($this->_readControl) {
$hashControl = @fread($fp, 32);
$length = $length - 32;
}
if ($length) {
$data = @fread($fp, $length);
} else {
$data = '';
}
set_magic_quotes_runtime($mqr);
if ($this->_fileLocking) @flock($fp, LOCK_UN);
@fclose($fp);
if ($this->_readControl) {
$hashData = $this->_hash($data, $this->_readControlType);
if ($hashData != $hashControl) {
if (!(is_null($this->_lifeTime))) {
@touch($this->_file, time() - 2*abs($this->_lifeTime));
} else {
@unlink($this->_file);
}
return false;
}
}
return $data;
}
return $this->raiseError('Cache_Lite : Unable to read cache !', -2);
}
/**
* Write the given data in the cache file
*
* @param string $data data to put in cache
* @return boolean true if ok (a PEAR_Error object else)
* @access private
*/
function _write($data)
{
if ($this->_hashedDirectoryLevel > 0) {
$hash = md5($this->_fileName);
$root = $this->_cacheDir;
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
if (!(@is_dir($root))) {
@mkdir($root, $this->_hashedDirectoryUmask);
}
}
}
$fp = @fopen($this->_file, "wb");
if ($fp) {
if ($this->_fileLocking) @flock($fp, LOCK_EX);
if ($this->_readControl) {
@fwrite($fp, $this->_hash($data, $this->_readControlType), 32);
}
$len = strlen($data);
@fwrite($fp, $data, $len);
if ($this->_fileLocking) @flock($fp, LOCK_UN);
@fclose($fp);
return true;
}
return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1);
}
/**
* Write the given data in the cache file and control it just after to avoir corrupted cache entries
*
* @param string $data data to put in cache
* @return boolean true if the test is ok (else : false or a PEAR_Error object)
* @access private
*/
function _writeAndControl($data)
{
$result = $this->_write($data);
if (is_object($result)) {
return $result; # We return the PEAR_Error object
}
$dataRead = $this->_read();
if (is_object($dataRead)) {
return $result; # We return the PEAR_Error object
}
if ((is_bool($dataRead)) && (!$dataRead)) {
return false;
}
return ($dataRead==$data);
}
/**
* Make a control key with the string containing datas
*
* @param string $data data
* @param string $controlType type of control 'md5', 'crc32' or 'strlen'
* @return string control key
* @access private
*/
function _hash($data, $controlType)
{
switch ($controlType) {
case 'md5':
return md5($data);
case 'crc32':
return sprintf('% 32d', crc32($data));
case 'strlen':
return sprintf('% 32d', strlen($data));
default:
return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5);
}
}
}
/**
* This class extends Cache_Lite and offers a cache system driven by a master
* file or timestamp
*
* With this class, cache validity is only dependent of a given file or timestamp.
* Cache files are valid only if they are older than the master file or the given
* timestamp. It's a perfect way for caching templates results (if the template
* file is newer than the cache, cache must be rebuild...) or for config classes...
*
* If the cache is dependent on multiple files, supply the constructor's
* 'masterTime' option with the greatest of the files' mtimes.
*
* There are some examples in the 'docs/examples' file
* Technical choices are described in the 'docs/technical' file
*
* @package Cache_Lite
* @version $Id: File.php,v 1.3 2005/12/04 16:03:55 fab Exp $
* @author Fabien MARTY <fab@php.net>
*/
// require_once('Cache/Lite.php');
class Cache_Lite_File extends Cache_Lite
{
// --- Private properties ---
/**
* Complete path of the file used for controlling the cache lifetime
*
* @var string $_masterFile
*/
var $_masterFile = '';
/**
* Masterfile mtime
*
* @var int $_masterFile_mtime
*/
var $_masterFile_mtime = 0;
// --- Public methods ----
/**
* Constructor
*
* $options is an assoc. To have a look at availables options,
* see the constructor of the Cache_Lite class in 'Cache_Lite.php'
*
* Comparing to Cache_Lite constructor, there are two more options:
* $options = array(
* (...) see Cache_Lite constructor
* 'masterFile' => complete path of the file used for controlling the cache lifetime(string)
* 'masterTime' => timestamp of last application change that would invalidate the cache(int).
* );
* Supply only one of these. If 'masterFile' is supplied, 'masterTime' is
* ignored, otherwise 'masterTime' is required.
*
* @param array $options options
* @access public
*/
function Cache_Lite_File($options = array(NULL))
{
$options['lifetime'] = 0;
$this->Cache_Lite($options);
if (isset($options['masterFile'])) {
$this->_masterFile = $options['masterFile'];
if (!($this->_masterFile_mtime = @filemtime($this->_masterFile))) {
return $this->raiseError('Cache_Lite_File : Unable to read masterFile : '.$this->_masterFile, -3);
}
} elseif (isset($options['masterTime'])) {
$this->_masterFile_mtime = $options['masterTime'];
} else {
return $this->raiseError('Cache_Lite_File : either masterFile or masterTime option must be set !');
}
}
/**
* Test if a cache is available and (if yes) return it
*
* The third param is just to suppress PHP5's E_STRICT warning
*
* @param string $id cache id
* @param string $group name of the cache group
* @return string data of the cache (or false if no cache available)
* @access public
*/
function get($id, $group = 'default', $thisValueIgnored = false)
{
if ($data = parent::get($id, $group, true)) {
if ($filemtime = $this->lastModified()) {
if ($filemtime > $this->_masterFile_mtime) {
return $data;
}
}
}
return false;
}
}

View File

@@ -1,6 +0,0 @@
File.php contains PEAR's Cache_Lite and a patched version of Cache_Lite_File.
See: http://pear.php.net/bugs/bug.php?id=12179
Until the patch is accepted (hopefully), we'll include it here.

View File

@@ -36,25 +36,41 @@ class Minify {
const TYPE_JS = 'application/x-javascript'; const TYPE_JS = 'application/x-javascript';
/** /**
* Specify a writeable directory for cache files. If not called, Minify * @see setCache()
* will not use a disk cache and, for each 200 response, will need to * @param mixed $cache object with identical interface as Minify_Cache_File or
* recombine files, minify and encode the output. * a directory path. (default = '')
* @return null
* @deprecated
*/
public static function useServerCache($path = null)
{
self::setCache($path);
}
/**
* Specify a cache object (with identical interface as Minify_Cache_File) or
* a path to use with Minify_Cache_File.
* *
* @param string $path Full directory path for cache files (should not end * If not called, Minify will not use a cache and, for each 200 response, will
* in directory separator character). If not provided, Minify will attempt to * need to recombine files, minify and encode the output.
* write to the path returned by sys_get_temp_dir(). *
* @param mixed $cache object with identical interface as Minify_Cache_File or
* a directory path. (default = '')
* *
* @return null * @return null
*/ */
public static function useServerCache($path = null) { public static function setCache($cache = '')
if (null !== $path) { {
self::$_cachePath = $path; if (is_string($cache)) {
require_once 'Minify/Cache/File.php';
self::$_cache = new Minify_Cache_File($cache);
} else { } else {
require_once 'Solar/Dir.php'; self::$_cache = $cache;
self::$_cachePath = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
} }
} }
private static $_cache = null;
/** /**
* Serve a request for a minified file. * Serve a request for a minified file.
* *
@@ -82,14 +98,23 @@ class Minify {
* E.g. ($_SERVER['REQUEST_TIME'] + 86400 * 365) for 1yr * E.g. ($_SERVER['REQUEST_TIME'] + 86400 * 365) for 1yr
* Note this has nothing to do with server-side caching. * Note this has nothing to do with server-side caching.
* *
* 'perType' : this is an array of options to send to a particular minifier * 'minifiers' : to override Minify's default choice of minifier function for
* function using the content-type as key. E.g. To send the CSS minifier an * a particular content-type, specify your callback under the key of the
* option: * content-type:
* <code> * <code>
* $options['perType'][Minify::TYPE_CSS]['optionName'] = 'optionValue'; * // call customCssMinifier($css) for all CSS minification
* $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
*
* // don't minify Javascript at all
* $options['minifiers'][Minify::TYPE_JS] = '';
* </code>
*
* 'minifierOptions' : to send options to the minifier function, specify your options
* under the key of the content-type. E.g. To send the CSS minifier an option:
* <code>
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
* $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
* </code> * </code>
* When the CSS minifier is called, the 2nd argument will be
* array('optionName' => 'optionValue').
* *
* Any controller options are documented in that controller's setupSources() method. * Any controller options are documented in that controller's setupSources() method.
* *
@@ -191,7 +216,7 @@ class Minify {
self::$_options['encodeMethod'] = ''; // identity (no encoding) self::$_options['encodeMethod'] = ''; // identity (no encoding)
} }
if (null !== self::$_cachePath) { if (null !== self::$_cache) {
// using cache // using cache
// the goal is to use only the cache methods to sniff the length and // the goal is to use only the cache methods to sniff the length and
// output the content, as they do not require ever loading the file into // output the content, as they do not require ever loading the file into
@@ -204,14 +229,15 @@ class Minify {
: ''; : '';
$fullCacheId = $cacheId . $encodingExtension; $fullCacheId = $cacheId . $encodingExtension;
// check cache for valid entry // check cache for valid entry
$cacheContentLength = self::getCacheSize($fullCacheId, self::$_options['lastModifiedTime']); $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
$cacheIsReady = (false !== $cacheContentLength); if ($cacheIsReady) {
if (! $cacheIsReady) { $cacheContentLength = self::$_cache->getSize($fullCacheId);
} else {
// generate & cache content // generate & cache content
$content = self::_combineMinify(); $content = self::_combineMinify();
self::writeCache($cacheId, $content); self::$_cache->store($cacheId, $content);
self::writeCache($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel'])); self::$_cache->store($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel']));
self::writeCache($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel'])); self::$_cache->store($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel']));
} }
} else { } else {
// no cache // no cache
@@ -243,7 +269,7 @@ class Minify {
header($name . ': ' . $val); header($name . ': ' . $val);
} }
if ($cacheIsReady) { if ($cacheIsReady) {
self::outputCache($fullCacheId); self::$_cache->display($fullCacheId);
} else { } else {
echo $content; echo $content;
} }
@@ -252,18 +278,13 @@ class Minify {
'success' => true 'success' => true
,'statusCode' => 200 ,'statusCode' => 200
,'content' => $cacheIsReady ,'content' => $cacheIsReady
? self::getCache($fullCacheId) ? self::$_cache->fetch($fullCacheId)
: $content : $content
,'headers' => $headers ,'headers' => $headers
); );
} }
} }
/**
* @var mixed null if disk cache is not to be used
*/
private static $_cachePath = null;
/** /**
* @var Minify_Controller active controller for current request * @var Minify_Controller active controller for current request
*/ */
@@ -290,8 +311,8 @@ class Minify {
// allow the user to pass a particular array of options to each // allow the user to pass a particular array of options to each
// minifier (designated by type). source objects may still override // minifier (designated by type). source objects may still override
// these // these
$defaultOptions = isset(self::$_options['perType'][$type]) $defaultOptions = isset(self::$_options['minifierOptions'][$type])
? self::$_options['perType'][$type] ? self::$_options['minifierOptions'][$type]
: array(); : array();
// if minifier not set, default is no minification. source objects // if minifier not set, default is no minification. source objects
// may still override this // may still override this
@@ -352,77 +373,7 @@ class Minify {
return md5(serialize(array( return md5(serialize(array(
Minify_Source::getDigest(self::$_controller->sources) Minify_Source::getDigest(self::$_controller->sources)
,self::$_options['minifiers'] ,self::$_options['minifiers']
,self::$_options['perType'] ,self::$_options['minifierOptions']
))); )));
} }
/**
* Write data to file and verify its contents
*
* @param string $file full file path
*
* @param string $data
*
* @return bool success
*/
protected static function _verifiedWrite($file, $data)
{
return (file_put_contents($file, $data, LOCK_EX)
&& (md5($data) === md5_file($file))
);
}
/**
* Write data to cache.
*
* @param string $id cache id (e.g. a filename)
*
* @param string $data
*
* @return bool success
*/
protected static function writeCache($id, $data)
{
return self::_verifiedWrite(self::$_cachePath . '/' . $id, $data);
}
/**
* Get the size of a valid cache entry (if exists)
*
* @param string $id cache id (e.g. a filename)
*
* @param int $srcMtime mtime of the original source file(s)
*
* @return mixed number of bytes on success, false if file doesn't
* exist or is stale
*/
protected static function getCacheSize($id, $srcMtime)
{
$file = self::$_cachePath . '/' . $id;
return (file_exists($file) && (filemtime($file) >= $srcMtime))
? filesize($file)
: false;
}
/**
* Send the cached content to output
*
* @param string $id cache id (e.g. a filename)
*/
protected static function outputCache($id)
{
readfile(self::$_cachePath . '/' . $id);
}
/**
* Fetch the cached content
*
* @param string $id cache id (e.g. a filename)
*
* @return string
*/
protected static function getCache($id)
{
return file_get_contents(self::$_cachePath . '/' . $id);
}
} }

98
lib/Minify/Cache/File.php Normal file
View File

@@ -0,0 +1,98 @@
<?php
/**
* Class Minify_Cache_File
* @package Minify
*/
class Minify_Cache_File {
public function __construct($path = '')
{
if (! $path) {
require_once 'Solar/Dir.php';
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
}
$this->_path = $path;
}
/**
* Write data to cache.
*
* @param string $id cache id (e.g. a filename)
*
* @param string $data
*
* @return bool success
*/
public function store($id, $data)
{
return self::_verifiedWrite($this->_path . '/' . $id, $data);
}
/**
* Get the size of a cache entry
*
* @param string $id cache id (e.g. a filename)
*
* @return int size in bytes
*/
public function getSize($id)
{
return filesize($this->_path . '/' . $id);
}
/**
* Does a valid cache entry exist?
*
* @param string $id cache id (e.g. a filename)
*
* @param int $srcMtime mtime of the original source file(s)
*
* @return bool exists
*/
public function isValid($id, $srcMtime)
{
$file = $this->_path . '/' . $id;
return (file_exists($file) && (filemtime($file) >= $srcMtime));
}
/**
* Send the cached content to output
*
* @param string $id cache id (e.g. a filename)
*/
public function display($id)
{
readfile($this->_path . '/' . $id);
}
/**
* Fetch the cached content
*
* @param string $id cache id (e.g. a filename)
*
* @return string
*/
public function fetch($id)
{
return file_get_contents($this->_path . '/' . $id);
}
private $_path = null;
/**
* Write data to file and verify its contents
*
* @param string $file path
*
* @param string $data
*
* @return bool success
*/
private static function _verifiedWrite($file, $data)
{
return (file_put_contents($file, $data, LOCK_EX)
&& (md5($data) === md5_file($file))
);
}
}

View File

@@ -43,7 +43,7 @@ abstract class Minify_Controller_Base {
,'encodeOutput' => true ,'encodeOutput' => true
,'encodeMethod' => null // determine later ,'encodeMethod' => null // determine later
,'encodeLevel' => 9 ,'encodeLevel' => 9
,'perType' => array() // no per-type minifier options ,'minifierOptions' => array() // no minifier options
,'contentTypeCharset' => 'UTF-8' ,'contentTypeCharset' => 'UTF-8'
,'setExpires' => null // use conditional GET ,'setExpires' => null // use conditional GET
,'quiet' => false // serve() will send headers and output ,'quiet' => false // serve() will send headers and output

View File

@@ -46,6 +46,10 @@ class Minify_Controller_Groups extends Minify_Controller_Base {
$groups = $options['groups']; $groups = $options['groups'];
unset($options['groups']); unset($options['groups']);
if (! isset($_SERVER['PATH_INFO'])) {
// no PATH_INFO
return $options;
}
$pi = substr($_SERVER['PATH_INFO'], 1); $pi = substr($_SERVER['PATH_INFO'], 1);
if (! isset($groups[$pi])) { if (! isset($groups[$pi])) {
// not a valid group // not a valid group

View File

@@ -31,7 +31,7 @@ class Minify_Controller_Version1 extends Minify_Controller_Base {
$cacheDir = defined('MINIFY_CACHE_DIR') $cacheDir = defined('MINIFY_CACHE_DIR')
? MINIFY_CACHE_DIR ? MINIFY_CACHE_DIR
: null; : null;
Minify::useServerCache($cacheDir); Minify::setCache($cacheDir);
} }
$options['badRequestHeader'] = 'HTTP/1.0 404 Not Found'; $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
$options['contentTypeCharset'] = MINIFY_ENCODING; $options['contentTypeCharset'] = MINIFY_ENCODING;

View File

@@ -5,7 +5,7 @@ require '../../config.php';
require 'Minify.php'; require 'Minify.php';
// give an explicit path to avoid having to load Solar/Dir.php // give an explicit path to avoid having to load Solar/Dir.php
Minify::useServerCache($minifyCachePath); Minify::setCache($minifyCachePath);
Minify::serve('Files', array( Minify::serve('Files', array(
'files' => array( 'files' => array(

View File

@@ -0,0 +1,15 @@
<?php
require '../../config.php';
require 'Minify.php';
// give an explicit path to avoid having to load Solar/Dir.php
Minify::setCache($minifyCachePath);
Minify::serve('Groups', array(
'groups' => array(
'test' => array(dirname(__FILE__) . '/before.js')
)
,'setExpires' => $_SERVER['REQUEST_TIME'] + 31536000 // 1 yr
));

View File

@@ -1,36 +1,43 @@
@SET PATH=%PATH%;C:\xampp\apache\bin @SET PATH=%PATH%;C:\xampp\apache\bin
@REM SET ABCALL=ab -d -S -c 100 -n 2000 -H "Accept-Encoding: deflate, gzip" http://localhost ::SET ABCALL=ab -d -S -c 100 -n 2000 -H "Accept-Encoding: deflate, gzip" http://localhost
@SET ABCALL=ab -d -S -c 100 -n 2000 -H "Accept-Encoding: deflate, gzip" http://mc.dev/_3rd_party @SET ABCALL=ab -d -S -c 100 -n 2000 -H "Accept-Encoding: deflate, gzip" http://mc.dev/_3rd_party
::SET TXTVIEWER=notepad.exe
@SET TXTVIEWER="C:\Program Files\Notepad++\notepad++.exe"
@SET DELIM=TYPE _delimiter @SET DELIM=TYPE _delimiter
DEL results.txt DEL results.txt
@REM baseline PHP :: baseline PHP
%ABCALL%/minify/web/ab_tests/ideal_php/before.php >> results.txt %ABCALL%/minify/web/ab_tests/ideal_php/before.php >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
@REM 1.0 release :: 1.0 release
%ABCALL%/minify/web/ab_tests/v1.0/minify.php?files=before.js >> results.txt %ABCALL%/minify/web/ab_tests/v1.0/minify.php?files=before.js >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
@REM Files controller :: Files controller
%ABCALL%/minify/web/ab_tests/minify/test_Files.php >> results.txt %ABCALL%/minify/web/ab_tests/minify/test_Files.php >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
@REM Version1 controller :: Groups controller
%ABCALL%/minify/web/ab_tests/minify/test_Groups.php/test >> results.txt
@%DELIM% >> results.txt
:: Version1 controller
%ABCALL%/minify/web/ab_tests/minify/test_Version1.php?files=before.js >> results.txt %ABCALL%/minify/web/ab_tests/minify/test_Version1.php?files=before.js >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
@REM mod_deflate :: mod_deflate
%ABCALL%/minify/web/ab_tests/mod_deflate/before.js >> results.txt %ABCALL%/minify/web/ab_tests/mod_deflate/before.js >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
@REM type-map :: type-map
%ABCALL%/minify/web/ab_tests/type-map/before.js.var >> results.txt %ABCALL%/minify/web/ab_tests/type-map/before.js.var >> results.txt
%DELIM% >> results.txt @%DELIM% >> results.txt
FINDSTR "Path: Length: Requests --" results.txt > results_summary.txt FINDSTR "Path: Length: Requests --" results.txt > results_summary.txt
notepad results_summary.txt START %TXTVIEWER% results_summary.txt

View File

@@ -498,4 +498,3 @@ class MinifyInvalidUrlException extends MinifyException {}
if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) { if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) {
Minify::handleRequest(); Minify::handleRequest();
} }
?>

View File

@@ -5,7 +5,7 @@ require '_groupsSources.php';
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::useServerCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
Minify::serve('Groups', array( Minify::serve('Groups', array(

View File

@@ -5,7 +5,7 @@ require '_groupsSources.php';
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::useServerCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
Minify::serve('Groups', array( Minify::serve('Groups', array(

View File

@@ -27,7 +27,7 @@ if (isset($_GET['f'])) {
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::useServerCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
// The Files controller can serve an array of files, but here we just // The Files controller can serve an array of files, but here we just

View File

@@ -84,7 +84,7 @@ function test_Minify()
// Test minifying CSS and responding with Etag/Last-Modified // Test minifying CSS and responding with Etag/Last-Modified
// needed to expose E_STRICT warning in Cache_Lite_File // needed to expose E_STRICT warning in Cache_Lite_File
Minify::useServerCache(); Minify::setCache();
// don't allow conditional headers // don't allow conditional headers
unset($_SERVER['HTTP_IF_NONE_MATCH'], $_SERVER['HTTP_IF_MODIFIED_SINCE']); unset($_SERVER['HTTP_IF_NONE_MATCH'], $_SERVER['HTTP_IF_MODIFIED_SINCE']);