3 * Class Minify_JS_ClosureCompiler
8 * Minify Javascript using Google's Closure Compiler API
10 * @link http://code.google.com/closure/compiler/
12 * @author Stephen Clay <steve@mrclay.org>
14 * @todo can use a stream wrapper to unit test this?
16 class Minify_JS_ClosureCompiler {
17 const URL = 'http://closure-compiler.appspot.com/compile';
20 * Minify Javascript code via HTTP request to the Closure Compiler API
22 * @param string $js input code
23 * @param array $options unused at this point
26 public static function minify($js, array $options = array())
28 $obj = new self($options);
29 return $obj->min($js);
34 * @param array $options
36 * fallbackFunc : default array($this, 'fallback');
38 public function __construct(array $options = array())
40 $this->_fallbackFunc = isset($options['fallbackMinifier'])
41 ? $options['fallbackMinifier']
42 : array($this, '_fallback');
45 public function min($js)
47 $postBody = $this->_buildPostBody($js);
48 $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
49 ? mb_strlen($postBody, '8bit')
51 if ($bytes > 200000) {
52 throw new Minify_JS_ClosureCompiler_Exception(
53 'POST content larger than 200000 bytes'
56 $response = $this->_getResponse($postBody);
57 if (preg_match('/^Error\(\d\d?\):/', $response)) {
58 if (is_callable($this->_fallbackFunc)) {
59 $response = "/* Received errors from Closure Compiler API:\n$response"
60 . "\n(Using fallback minifier)\n*/\n";
61 $response .= call_user_func($this->_fallbackFunc, $js);
63 throw new Minify_JS_ClosureCompiler_Exception($response);
66 if ($response === '') {
67 $errors = $this->_getResponse($this->_buildPostBody($js, true));
68 throw new Minify_JS_ClosureCompiler_Exception($errors);
73 protected $_fallbackFunc = null;
75 protected function _getResponse($postBody)
77 $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
79 $contents = file_get_contents(self::URL, false, stream_context_create(array(
82 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
83 'content' => $postBody,
88 } elseif (defined('CURLOPT_POST')) {
89 $ch = curl_init(self::URL);
90 curl_setopt($ch, CURLOPT_POST, true);
91 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
92 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
93 curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
94 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
95 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
96 $contents = curl_exec($ch);
99 throw new Minify_JS_ClosureCompiler_Exception(
100 "Could not make HTTP request: allow_url_open is false and cURL not available"
103 if (false === $contents) {
104 throw new Minify_JS_ClosureCompiler_Exception(
105 "No HTTP response from server"
108 return trim($contents);
111 protected function _buildPostBody($js, $returnErrors = false)
113 return http_build_query(array(
115 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
116 'output_format' => 'text',
117 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
122 * Default fallback function if CC API fails
126 protected function _fallback($js)
128 return JSMin::minify($js);
132 class Minify_JS_ClosureCompiler_Exception extends Exception {}