PageRenderTime 83ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Bal/Service/GoogleClosure.php

https://github.com/balupton/balphp
PHP | 345 lines | 211 code | 55 blank | 79 comment | 18 complexity | f83c690b5e3f1b39be0fb17f7e39b581 MD5 | raw file
  1. <?php
  2. /**
  3. * http://code.google.com/p/php-closure/source/browse/trunk/php-closure.php
  4. */
  5. class Bal_Service_GoogleClosure {
  6. /** The Default Compilation Level to Use **/
  7. protected $_compilationLevel = 'SIMPLE_OPTIMIZATIONS';
  8. /** The Default Warning Level to Use **/
  9. protected $_warningLevel = 'DEFAULT';
  10. /**
  11. * Compile a series of Files to the Output Path using the Google closure Web Service
  12. **/
  13. public function compile ( $files, $output_path ) {
  14. $compiled = $this->getResultFromFiles($files);
  15. file_put_contents($output_path,$compiled);
  16. }
  17. /**
  18. * Takes a File Group to be sent, and converts it into a series of Request Groups.
  19. * These Request Groups can then be sent to Google Closure
  20. **/
  21. protected function generateRequestGroups ( $fileGroup ) {
  22. # Prepare
  23. $requestGroups = array();
  24. $requestGroup = array();
  25. $total_size = $file_size = 0;
  26. $max_size = 800*1024;
  27. # Split into Size Groups
  28. foreach ( $fileGroup as $file ) {
  29. $file_path = $file['path'];
  30. $file_size = filesize($file_path);
  31. $total_size += $file_size;
  32. # Reached the end of a request
  33. if ( $total_size > $max_size ) {
  34. # Append requestGroup to requestGroups
  35. $requestGroups[] = $requestGroup;
  36. # Reset with Current File
  37. $total_size = $file_size;
  38. $requestGroup = array();
  39. }
  40. # Add file to the request
  41. $requestGroup[] = $file;
  42. }
  43. # Append requestGroup to requestGroups
  44. $requestGroups[] = $requestGroup;
  45. # Return requestGroups
  46. return $requestGroups;
  47. }
  48. /**
  49. * Send a Request Group to Google Closure
  50. **/
  51. protected function sendRequestGroup ( $request_group ) {
  52. # Perform Request
  53. $params = $this->generateParamsFromRequestGroup($request_group);
  54. $response = $this->sendRequestViaCurl($params);
  55. $result = $this->getResultFromResponse($response);
  56. # Return result
  57. return $result;
  58. }
  59. /**
  60. * Send a File Group
  61. **/
  62. protected function sendFileGroup ( $file_group ) {
  63. $result = '';
  64. $request_groups = $this->generateRequestGroups($file_group);
  65. foreach ( $request_groups as $request_group ) {
  66. $result .= ' '.$this->sendRequestGroup($request_group);
  67. }
  68. return $result;
  69. }
  70. /**
  71. * Gets the Result by processing and sending the group of passed files
  72. **/
  73. protected function getResultFromFiles ( $files ) {
  74. # Prepare
  75. $result = '';
  76. $group = array();
  77. # Combine
  78. foreach ( $files as $file ) {
  79. if ( $file['minified'] ) {
  80. $result .= ' '.$this->sendFileGroup($group); $group = array();
  81. $result .= ' '.file_get_contents($file['path']);
  82. }
  83. else {
  84. $group[] = $file;
  85. }
  86. }
  87. # Finish Up
  88. $result .= ' '.$this->sendFileGroup($group); $group = array();
  89. # Return result
  90. return $result;
  91. }
  92. /**
  93. * Process the Response and Return the Result
  94. */
  95. protected function getResultFromResponse ( $response ) {
  96. # Prepare
  97. $result = '';
  98. # Check Response
  99. if ( strpos($response, '<?xml') !== 0 ) {
  100. # Error
  101. throw new Bal_Exception(array(
  102. 'Errors occurred when using the Google Closure Service.',
  103. 'response' => $response
  104. ));
  105. }
  106. # Process Response
  107. $responseTree = $this->parseXml($response);
  108. $responseValue = $responseTree[0]['value'];
  109. # Cycle through Responses
  110. foreach ( $responseValue as $node ) {
  111. # Reset Response
  112. $code = $originalSize = $originalGzipSize = $compressedSize = $compressedGzipSize = $compileTime = $warnings = $errors = '';
  113. # Handle Tags
  114. switch ( $node['tag'] ) {
  115. case 'compiledCode':
  116. $code = $node['value'];
  117. break;
  118. case 'warnings':
  119. $warnings = $node['value'];
  120. break;
  121. case 'serverErrors':
  122. case 'errors':
  123. $errors = $node['value'];
  124. break;
  125. case 'statistics':
  126. foreach ($node['value'] as $stat) {
  127. switch ($stat['tag']) {
  128. case 'originalSize':
  129. case 'originalGzipSize':
  130. case 'compressedSize':
  131. case 'compressedGzipSize':
  132. case 'compileTime':
  133. $var = $stat['tag'];
  134. $$var = $stat['value'];
  135. break;
  136. default:
  137. break;
  138. }
  139. }
  140. break;
  141. }
  142. # Append the code
  143. if ( $code ) {
  144. $result .= ' '.$code;
  145. }
  146. }
  147. # No errors, but our result is empty - should be an error
  148. if ( !$result && !$errors && !$warnings ) {
  149. $errors = 'Result is empty';
  150. }
  151. # We have an error
  152. if ( $errors ) {
  153. $error = $this->flattenResponseDetail($errors);
  154. $warning = $this->flattenResponseDetail($warnings);
  155. # Error
  156. throw new Bal_Exception(array(
  157. 'Errors occurred when using the Google Closure Service.',
  158. 'error' => $error,
  159. 'warning' => $warning
  160. ));
  161. }
  162. # Return result
  163. return $result;
  164. }
  165. /**
  166. * Flatten a Response Detail
  167. **/
  168. protected function flattenResponseDetail ( $errors ) {
  169. $error = '';
  170. if ( is_array($errors) ) {
  171. foreach ( $errors as $_error ) {
  172. $error .= $_error['value'];
  173. }
  174. }
  175. else {
  176. $error = $errors;
  177. }
  178. return $error;
  179. }
  180. /**
  181. * Convert a Series of Params to a String ready for sending
  182. **/
  183. protected function generateParamsString ( $params ) {
  184. # Prepare
  185. $parts = array();;
  186. # Convert Params to Parts
  187. foreach ( $params as $key => $value) {
  188. $parts[] = preg_replace('/_[0-9]$/', '', $key) . '=' . urlencode($value);
  189. }
  190. # Generate String
  191. $result = implode('&', $parts);
  192. # Return result
  193. return $result;
  194. }
  195. /**
  196. * Generate a series of Params for Sending from the Group of Files
  197. **/
  198. protected function generateParamsFromRequestGroup ( $request_group ) {
  199. # Prepare
  200. $params = array();
  201. $compilationLevel = $this->_compilationLevel;
  202. # Add Code
  203. $params['js_code'] = '';
  204. foreach ( $request_group as $file ) {
  205. $file_code = file_get_contents($file['path']);
  206. $params['js_code'] .= $file_code;
  207. if ( !empty($file['compilationlevel']) ) {
  208. $compilationLevel = $file['compilationlevel'];
  209. }
  210. }
  211. # Add Params
  212. $params['compilation_level'] = $compilationLevel;
  213. $params['output_format'] = 'xml';
  214. $params['warning_level'] = $this->_warningLevel;
  215. $params['use_closure_library'] = 'true';
  216. $params['output_info_1'] = 'compiled_code';
  217. $params['output_info_2'] = 'statistics';
  218. $params['output_info_3'] = 'warnings';
  219. $params['output_info_4'] = 'errors';
  220. # Return params
  221. return $params;
  222. }
  223. /**
  224. * Send the Request via CURL
  225. **/
  226. protected function sendRequestViaCurl ( $params ) {
  227. # Generate Params String
  228. $paramsString = $this->generateParamsString($params);
  229. # Generate Options
  230. $options = array(
  231. CURLOPT_POST => 1,
  232. CURLOPT_HEADER => 0,
  233. CURLOPT_URL => 'closure-compiler.appspot.com/compile',
  234. CURLOPT_FRESH_CONNECT => 1,
  235. CURLOPT_RETURNTRANSFER => 1,
  236. CURLOPT_FORBID_REUSE => 1,
  237. CURLOPT_TIMEOUT => 60,
  238. CURLOPT_POSTFIELDS => $paramsString
  239. );
  240. # Open
  241. $channel = curl_init();
  242. curl_setopt_array($channel, $options);
  243. # Perform Request
  244. if ( !$result = curl_exec($channel) ) {
  245. throw new Exception('Could not connect to Google Closure Service: '.curl_error($channel));
  246. }
  247. # Close Channel
  248. curl_close($channel);
  249. # Return result
  250. return $result;
  251. }
  252. /**
  253. * Parse a XML Response
  254. **/
  255. protected function parseXml($data) {
  256. $data = str_replace('&lt;', '---LTLTLTLT---', $data);
  257. $xml = new XMLReader();
  258. $xml->xml($data);
  259. return $this->parseXmlHelper($xml);
  260. }
  261. protected function parseXmlHelper($xml) {
  262. $tree = null;
  263. while( $xml->read() ) {
  264. switch ( $xml->nodeType ) {
  265. case XMLReader::END_ELEMENT:
  266. return $tree;
  267. case XMLReader::ELEMENT:
  268. $node = array(
  269. 'tag' => $xml->name,
  270. 'value' => $xml->isEmptyElement ? '' : $this->parseXmlHelper($xml)
  271. );
  272. if ( $xml->hasAttributes ) {
  273. while ( $xml->moveToNextAttribute() ) {
  274. $node['attributes'][$xml->name] = str_replace('---LTLTLTLT---', '<', $xml->value);
  275. }
  276. }
  277. $tree[] = $node;
  278. break;
  279. case XMLReader::TEXT:
  280. case XMLReader::CDATA:
  281. $tree .= str_replace('---LTLTLTLT---', '<', $xml->value);
  282. break;
  283. default:
  284. throw new Exception('Unknown node type');
  285. break;
  286. }
  287. }
  288. return $tree;
  289. }
  290. }