PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/application/libraries/Format.php

http://github.com/philsturgeon/codeigniter-restserver
PHP | 524 lines | 247 code | 77 blank | 200 comment | 43 complexity | 1e16740fede0241394efd856d62e329e MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. defined('BASEPATH') OR exit('No direct script access allowed');
  3. /**
  4. * Format class
  5. * Help convert between various formats such as XML, JSON, CSV, etc.
  6. *
  7. * @author Phil Sturgeon, Chris Kacerguis, @softwarespot
  8. * @license http://www.dbad-license.org/
  9. */
  10. class Format {
  11. /**
  12. * Array output format
  13. */
  14. const ARRAY_FORMAT = 'array';
  15. /**
  16. * Comma Separated Value (CSV) output format
  17. */
  18. const CSV_FORMAT = 'csv';
  19. /**
  20. * Json output format
  21. */
  22. const JSON_FORMAT = 'json';
  23. /**
  24. * HTML output format
  25. */
  26. const HTML_FORMAT = 'html';
  27. /**
  28. * PHP output format
  29. */
  30. const PHP_FORMAT = 'php';
  31. /**
  32. * Serialized output format
  33. */
  34. const SERIALIZED_FORMAT = 'serialized';
  35. /**
  36. * XML output format
  37. */
  38. const XML_FORMAT = 'xml';
  39. /**
  40. * Default format of this class
  41. */
  42. const DEFAULT_FORMAT = self::JSON_FORMAT; // Couldn't be DEFAULT, as this is a keyword
  43. /**
  44. * CodeIgniter instance
  45. *
  46. * @var object
  47. */
  48. private $_CI;
  49. /**
  50. * Data to parse
  51. *
  52. * @var mixed
  53. */
  54. protected $_data = [];
  55. /**
  56. * Type to convert from
  57. *
  58. * @var string
  59. */
  60. protected $_from_type = NULL;
  61. /**
  62. * DO NOT CALL THIS DIRECTLY, USE factory()
  63. *
  64. * @param NULL $data
  65. * @param NULL $from_type
  66. * @throws Exception
  67. */
  68. public function __construct($data = NULL, $from_type = NULL)
  69. {
  70. // Get the CodeIgniter reference
  71. $this->_CI = &get_instance();
  72. // Load the inflector helper
  73. $this->_CI->load->helper('inflector');
  74. // If the provided data is already formatted we should probably convert it to an array
  75. if ($from_type !== NULL)
  76. {
  77. if (method_exists($this, '_from_'.$from_type))
  78. {
  79. $data = call_user_func([$this, '_from_'.$from_type], $data);
  80. }
  81. else
  82. {
  83. throw new Exception('Format class does not support conversion from "'.$from_type.'".');
  84. }
  85. }
  86. // Set the member variable to the data passed
  87. $this->_data = $data;
  88. }
  89. /**
  90. * Create an instance of the format class
  91. * e.g: echo $this->format->factory(['foo' => 'bar'])->to_csv();
  92. *
  93. * @param mixed $data Data to convert/parse
  94. * @param string $from_type Type to convert from e.g. json, csv, html
  95. *
  96. * @return object Instance of the format class
  97. */
  98. public function factory($data, $from_type = NULL)
  99. {
  100. // $class = __CLASS__;
  101. // return new $class();
  102. return new static($data, $from_type);
  103. }
  104. // FORMATTING OUTPUT ---------------------------------------------------------
  105. /**
  106. * Format data as an array
  107. *
  108. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  109. * to the constructor
  110. * @return array Data parsed as an array; otherwise, an empty array
  111. */
  112. public function to_array($data = NULL)
  113. {
  114. // If no data is passed as a parameter, then use the data passed
  115. // via the constructor
  116. if ($data === NULL && func_num_args() === 0)
  117. {
  118. $data = $this->_data;
  119. }
  120. // Cast as an array if not already
  121. if (is_array($data) === FALSE)
  122. {
  123. $data = (array) $data;
  124. }
  125. $array = [];
  126. foreach ((array) $data as $key => $value)
  127. {
  128. if (is_object($value) === TRUE || is_array($value) === TRUE)
  129. {
  130. $array[$key] = $this->to_array($value);
  131. }
  132. else
  133. {
  134. $array[$key] = $value;
  135. }
  136. }
  137. return $array;
  138. }
  139. /**
  140. * Format data as XML
  141. *
  142. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  143. * to the constructor
  144. * @param NULL $structure
  145. * @param string $basenode
  146. * @return mixed
  147. */
  148. public function to_xml($data = NULL, $structure = NULL, $basenode = 'xml')
  149. {
  150. if ($data === NULL && func_num_args() === 0)
  151. {
  152. $data = $this->_data;
  153. }
  154. if ($structure === NULL)
  155. {
  156. $structure = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$basenode />");
  157. }
  158. // Force it to be something useful
  159. if (is_array($data) === FALSE && is_object($data) === FALSE)
  160. {
  161. $data = (array) $data;
  162. }
  163. foreach ($data as $key => $value)
  164. {
  165. //change false/true to 0/1
  166. if (is_bool($value))
  167. {
  168. $value = (int) $value;
  169. }
  170. // no numeric keys in our xml please!
  171. if (is_numeric($key))
  172. {
  173. // make string key...
  174. $key = (singular($basenode) != $basenode) ? singular($basenode) : 'item';
  175. }
  176. // replace anything not alpha numeric
  177. $key = preg_replace('/[^a-z_\-0-9]/i', '', $key);
  178. if ($key === '_attributes' && (is_array($value) || is_object($value)))
  179. {
  180. $attributes = $value;
  181. if (is_object($attributes))
  182. {
  183. $attributes = get_object_vars($attributes);
  184. }
  185. foreach ($attributes as $attribute_name => $attribute_value)
  186. {
  187. $structure->addAttribute($attribute_name, $attribute_value);
  188. }
  189. }
  190. // if there is another array found recursively call this function
  191. elseif (is_array($value) || is_object($value))
  192. {
  193. $node = $structure->addChild($key);
  194. // recursive call.
  195. $this->to_xml($value, $node, $key);
  196. }
  197. else
  198. {
  199. // add single node.
  200. $value = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8');
  201. $structure->addChild($key, $value);
  202. }
  203. }
  204. return $structure->asXML();
  205. }
  206. /**
  207. * Format data as HTML
  208. *
  209. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  210. * to the constructor
  211. * @return mixed
  212. */
  213. public function to_html($data = NULL)
  214. {
  215. // If no data is passed as a parameter, then use the data passed
  216. // via the constructor
  217. if ($data === NULL && func_num_args() === 0)
  218. {
  219. $data = $this->_data;
  220. }
  221. // Cast as an array if not already
  222. if (is_array($data) === FALSE)
  223. {
  224. $data = (array) $data;
  225. }
  226. // Check if it's a multi-dimensional array
  227. if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE))
  228. {
  229. // Multi-dimensional array
  230. $headings = array_keys($data[0]);
  231. }
  232. else
  233. {
  234. // Single array
  235. $headings = array_keys($data);
  236. $data = [$data];
  237. }
  238. // Load the table library
  239. $this->_CI->load->library('table');
  240. $this->_CI->table->set_heading($headings);
  241. foreach ($data as $row)
  242. {
  243. // Suppressing the "array to string conversion" notice
  244. // Keep the "evil" @ here
  245. $row = @array_map('strval', $row);
  246. $this->_CI->table->add_row($row);
  247. }
  248. return $this->_CI->table->generate();
  249. }
  250. /**
  251. * @link http://www.metashock.de/2014/02/create-csv-file-in-memory-php/
  252. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  253. * to the constructor
  254. * @param string $delimiter The optional delimiter parameter sets the field
  255. * delimiter (one character only). NULL will use the default value (,)
  256. * @param string $enclosure The optional enclosure parameter sets the field
  257. * enclosure (one character only). NULL will use the default value (")
  258. * @return string A csv string
  259. */
  260. public function to_csv($data = NULL, $delimiter = ',', $enclosure = '"')
  261. {
  262. // Use a threshold of 1 MB (1024 * 1024)
  263. $handle = fopen('php://temp/maxmemory:1048576', 'w');
  264. if ($handle === FALSE)
  265. {
  266. return NULL;
  267. }
  268. // If no data is passed as a parameter, then use the data passed
  269. // via the constructor
  270. if ($data === NULL && func_num_args() === 0)
  271. {
  272. $data = $this->_data;
  273. }
  274. // If NULL, then set as the default delimiter
  275. if ($delimiter === NULL)
  276. {
  277. $delimiter = ',';
  278. }
  279. // If NULL, then set as the default enclosure
  280. if ($enclosure === NULL)
  281. {
  282. $enclosure = '"';
  283. }
  284. // Cast as an array if not already
  285. if (is_array($data) === FALSE)
  286. {
  287. $data = (array) $data;
  288. }
  289. // Check if it's a multi-dimensional array
  290. if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE))
  291. {
  292. // Multi-dimensional array
  293. $headings = array_keys($data[0]);
  294. }
  295. else
  296. {
  297. // Single array
  298. $headings = array_keys($data);
  299. $data = [$data];
  300. }
  301. // Apply the headings
  302. fputcsv($handle, $headings, $delimiter, $enclosure);
  303. foreach ($data as $record)
  304. {
  305. // If the record is not an array, then break. This is because the 2nd param of
  306. // fputcsv() should be an array
  307. if (is_array($record) === FALSE)
  308. {
  309. break;
  310. }
  311. // Suppressing the "array to string conversion" notice.
  312. // Keep the "evil" @ here.
  313. $record = @ array_map('strval', $record);
  314. // Returns the length of the string written or FALSE
  315. fputcsv($handle, $record, $delimiter, $enclosure);
  316. }
  317. // Reset the file pointer
  318. rewind($handle);
  319. // Retrieve the csv contents
  320. $csv = stream_get_contents($handle);
  321. // Close the handle
  322. fclose($handle);
  323. return $csv;
  324. }
  325. /**
  326. * Encode data as json
  327. *
  328. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  329. * to the constructor
  330. * @return string Json representation of a value
  331. */
  332. public function to_json($data = NULL)
  333. {
  334. // If no data is passed as a parameter, then use the data passed
  335. // via the constructor
  336. if ($data === NULL && func_num_args() === 0)
  337. {
  338. $data = $this->_data;
  339. }
  340. // Get the callback parameter (if set)
  341. $callback = $this->_CI->input->get('callback');
  342. if (empty($callback) === TRUE)
  343. {
  344. return json_encode($data, JSON_NUMERIC_CHECK);
  345. }
  346. // We only honour a jsonp callback which are valid javascript identifiers
  347. elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback))
  348. {
  349. // Return the data as encoded json with a callback
  350. return $callback.'('.json_encode($data, JSON_NUMERIC_CHECK).');';
  351. }
  352. // An invalid jsonp callback function provided.
  353. // Though I don't believe this should be hardcoded here
  354. $data['warning'] = 'INVALID JSONP CALLBACK: '.$callback;
  355. return json_encode($data);
  356. }
  357. /**
  358. * Encode data as a serialized array
  359. *
  360. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  361. * to the constructor
  362. * @return string Serialized data
  363. */
  364. public function to_serialized($data = NULL)
  365. {
  366. // If no data is passed as a parameter, then use the data passed
  367. // via the constructor
  368. if ($data === NULL && func_num_args() === 0)
  369. {
  370. $data = $this->_data;
  371. }
  372. return serialize($data);
  373. }
  374. /**
  375. * Format data using a PHP structure
  376. *
  377. * @param mixed|NULL $data Optional data to pass, so as to override the data passed
  378. * to the constructor
  379. * @return mixed String representation of a variable
  380. */
  381. public function to_php($data = NULL)
  382. {
  383. // If no data is passed as a parameter, then use the data passed
  384. // via the constructor
  385. if ($data === NULL && func_num_args() === 0)
  386. {
  387. $data = $this->_data;
  388. }
  389. return var_export($data, TRUE);
  390. }
  391. // INTERNAL FUNCTIONS
  392. /**
  393. * @param $data XML string
  394. * @return SimpleXMLElement XML element object; otherwise, empty array
  395. */
  396. protected function _from_xml($data)
  397. {
  398. return $data ? (array) simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA) : [];
  399. }
  400. /**
  401. * @param string $data CSV string
  402. * @param string $delimiter The optional delimiter parameter sets the field
  403. * delimiter (one character only). NULL will use the default value (,)
  404. * @param string $enclosure The optional enclosure parameter sets the field
  405. * enclosure (one character only). NULL will use the default value (")
  406. * @return array A multi-dimensional array with the outer array being the number of rows
  407. * and the inner arrays the individual fields
  408. */
  409. protected function _from_csv($data, $delimiter = ',', $enclosure = '"')
  410. {
  411. // If NULL, then set as the default delimiter
  412. if ($delimiter === NULL)
  413. {
  414. $delimiter = ',';
  415. }
  416. // If NULL, then set as the default enclosure
  417. if ($enclosure === NULL)
  418. {
  419. $enclosure = '"';
  420. }
  421. return str_getcsv($data, $delimiter, $enclosure);
  422. }
  423. /**
  424. * @param $data Encoded json string
  425. * @return mixed Decoded json string with leading and trailing whitespace removed
  426. */
  427. protected function _from_json($data)
  428. {
  429. return json_decode(trim($data));
  430. }
  431. /**
  432. * @param string Data to unserialized
  433. * @return mixed Unserialized data
  434. */
  435. protected function _from_serialize($data)
  436. {
  437. return unserialize(trim($data));
  438. }
  439. /**
  440. * @param $data Data to trim leading and trailing whitespace
  441. * @return string Data with leading and trailing whitespace removed
  442. */
  443. protected function _from_php($data)
  444. {
  445. return trim($data);
  446. }
  447. }