PageRenderTime 64ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/underscore.php

http://github.com/brianhaveri/Underscore.php
PHP | 1120 lines | 718 code | 289 blank | 113 comment | 121 complexity | 37edad5646a65baba696610efa18a35d MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Underscore.php v1.3.1
  4. * Copyright (c) 2011 Brian Haveri
  5. * Underscore.php is licensed under the MIT license
  6. * Underscore.php was inspired by and borrowed from Underscore.js
  7. * For docs, license, tests, and downloads, see: http://brianhaveri.github.com/Underscore.php
  8. */
  9. // Returns an instance of __ for OO-style calls
  10. function __($item=null) {
  11. $__ = new __;
  12. if(func_num_args() > 0) $__->_wrapped = $item;
  13. return $__;
  14. }
  15. // Underscore.php
  16. class __ {
  17. // Start the chain
  18. private $_chained = false; // Are we in a chain?
  19. public function chain($item=null) {
  20. list($item) = self::_wrapArgs(func_get_args(), 1);
  21. $__ = (isset($this) && isset($this->_chained) && $this->_chained) ? $this : __($item);
  22. $__->_chained = true;
  23. return $__;
  24. }
  25. // End the chain
  26. public function value() {
  27. return (isset($this)) ? $this->_wrapped : null;
  28. }
  29. // Invoke the iterator on each item in the collection
  30. public function each($collection=null, $iterator=null) {
  31. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  32. if(is_null($collection)) return self::_wrap(null);
  33. $collection = (array) self::_collection($collection);
  34. if(count($collection) === 0) return self::_wrap(null);
  35. foreach($collection as $k=>$v) {
  36. call_user_func($iterator, $v, $k, $collection);
  37. }
  38. return self::_wrap(null);
  39. }
  40. // Return an array of values by mapping each item through the iterator
  41. // map alias: collect
  42. public function collect($collection=null, $iterator=null) { return self::map($collection, $iterator); }
  43. public function map($collection=null, $iterator=null) {
  44. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  45. if(is_null($collection)) return self::_wrap(array());
  46. $collection = (array) self::_collection($collection);
  47. if(count($collection) === 0) self::_wrap(array());
  48. $return = array();
  49. foreach($collection as $k=>$v) {
  50. $return[] = call_user_func($iterator, $v, $k, $collection);
  51. }
  52. return self::_wrap($return);
  53. }
  54. // Reduce a collection to a single value
  55. // reduce aliases: foldl, inject
  56. public function foldl($collection=null, $iterator=null, $memo=null) { return self::reduce($collection, $iterator, $memo); }
  57. public function inject($collection=null, $iterator=null, $memo=null) { return self::reduce($collection, $iterator, $memo); }
  58. public function reduce($collection=null, $iterator=null, $memo=null) {
  59. list($collection, $iterator, $memo) = self::_wrapArgs(func_get_args(), 3);
  60. if(!is_object($collection) && !is_array($collection)) {
  61. if(is_null($memo)) throw new Exception('Invalid object');
  62. else return self::_wrap($memo);
  63. }
  64. return self::_wrap(array_reduce($collection, $iterator, $memo));
  65. }
  66. // Right-associative version of reduce
  67. // reduceRight alias: foldr
  68. public function foldr($collection=null, $iterator=null, $memo=null) { return self::reduceRight($collection, $iterator, $memo); }
  69. public function reduceRight($collection=null, $iterator=null, $memo=null) {
  70. list($collection, $iterator, $memo) = self::_wrapArgs(func_get_args(), 3);
  71. if(!is_object($collection) && !is_array($collection)) {
  72. if(is_null($memo)) throw new Exception('Invalid object');
  73. else return self::_wrap($memo);
  74. }
  75. krsort($collection);
  76. $__ = new self;
  77. return self::_wrap($__->reduce($collection, $iterator, $memo));
  78. }
  79. // Extract an array of values for a given property
  80. public function pluck($collection=null, $key=null) {
  81. list($collection, $key) = self::_wrapArgs(func_get_args(), 2);
  82. $collection = (array) self::_collection($collection);
  83. $return = array();
  84. foreach($collection as $item) {
  85. foreach($item as $k=>$v) {
  86. if($k === $key) $return[] = $v;
  87. }
  88. }
  89. return self::_wrap($return);
  90. }
  91. // Does the collection contain this value?
  92. // includ alias: contains
  93. public function contains($collection=null, $val=null) { return self::includ($collection, $val); }
  94. public function includ($collection=null, $val=null) {
  95. list($collection, $val) = self::_wrapArgs(func_get_args(), 2);
  96. $collection = (array) self::_collection($collection);
  97. return self::_wrap((array_search($val, $collection, true) !== false));
  98. }
  99. // Invoke the named function over each item in the collection, optionally passing arguments to the function
  100. public function invoke($collection=null, $function_name=null, $arguments=null) {
  101. $args = self::_wrapArgs(func_get_args(), 2);
  102. $__ = new self;
  103. list($collection, $function_name) = $__->first($args, 2);
  104. $arguments = $__->rest(func_get_args(), 2);
  105. // If passed an array or string, return an array
  106. // If passed an object, return an object
  107. $is_obj = is_object($collection);
  108. $result = (empty($arguments)) ? array_map($function_name, (array) $collection) : array_map($function_name, (array) $collection, $arguments);
  109. if($is_obj) $result = (object) $result;
  110. return self::_wrap($result);
  111. }
  112. // Does any values in the collection meet the iterator's truth test?
  113. // any alias: some
  114. public function some($collection=null, $iterator=null) { return self::any($collection, $iterator); }
  115. public function any($collection=null, $iterator=null) {
  116. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  117. $collection = self::_collection($collection);
  118. $__ = new self;
  119. if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
  120. if(count($collection) === 0) return self::_wrap(false);
  121. return self::_wrap(is_int(array_search(true, $collection, false)));
  122. }
  123. // Do all values in the collection meet the iterator's truth test?
  124. // all alias: every
  125. public function every($collection=null, $iterator=null) { return self::all($collection, $iterator); }
  126. public function all($collection=null, $iterator=null) {
  127. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  128. $collection = self::_collection($collection);
  129. $__ = new self;
  130. if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
  131. $collection = (array) $collection;
  132. if(count($collection) === 0) return true;
  133. return self::_wrap(is_bool(array_search(false, $collection, false)));
  134. }
  135. // Return an array of values that pass the truth iterator test
  136. // filter alias: select
  137. public function select($collection=null, $iterator=null) { return self::filter($collection, $iterator); }
  138. public function filter($collection=null, $iterator=null) {
  139. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  140. $collection = self::_collection($collection);
  141. $return = array();
  142. foreach($collection as $val) {
  143. if(call_user_func($iterator, $val)) $return[] = $val;
  144. }
  145. return self::_wrap($return);
  146. }
  147. // Return an array where the items failing the truth test are removed
  148. public function reject($collection=null, $iterator=null) {
  149. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  150. $collection = self::_collection($collection);
  151. $return = array();
  152. foreach($collection as $val) {
  153. if(!call_user_func($iterator, $val)) $return[] = $val;
  154. }
  155. return self::_wrap($return);
  156. }
  157. // Return the value of the first item passing the truth iterator test
  158. // find alias: detect
  159. public function detect($collection=null, $iterator=null) { return self::find($collection, $iterator); }
  160. public function find($collection=null, $iterator=null) {
  161. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  162. $collection = self::_collection($collection);
  163. foreach($collection as $val) {
  164. if(call_user_func($iterator, $val)) return $val;
  165. }
  166. return self::_wrap(false);
  167. }
  168. // How many items are in this collection?
  169. public function size($collection=null) {
  170. list($collection) = self::_wrapArgs(func_get_args(), 1);
  171. $collection = self::_collection($collection);
  172. return self::_wrap(count((array) $collection));
  173. }
  174. // Get the first element of an array. Passing n returns the first n elements.
  175. // first alias: head
  176. public function head($collection=null, $n=null) { return self::first($collection, $n); }
  177. public function first($collection=null, $n=null) {
  178. list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
  179. $collection = self::_collection($collection);
  180. if($n === 0) return self::_wrap(array());
  181. if(is_null($n)) return self::_wrap(current(array_splice($collection, 0, 1, true)));
  182. return self::_wrap(array_splice($collection, 0, $n, true));
  183. }
  184. // Get the rest of the array elements. Passing n returns from that index onward.
  185. public function tail($collection=null, $index=null) { return self::rest($collection, $index); }
  186. public function rest($collection=null, $index=null) {
  187. list($collection, $index) = self::_wrapArgs(func_get_args(), 2);
  188. if(is_null($index)) $index = 1;
  189. $collection = self::_collection($collection);
  190. return self::_wrap(array_splice($collection, $index));
  191. }
  192. // Return everything but the last array element. Passing n excludes the last n elements.
  193. public function initial($collection=null, $n=null) {
  194. list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
  195. $collection = (array) self::_collection($collection);
  196. if(is_null($n)) $n = 1;
  197. $first_index = count($collection) - $n;
  198. $__ = new self;
  199. return self::_wrap($__->first($collection, $first_index));
  200. }
  201. // Get the last element from an array. Passing n returns the last n elements.
  202. public function last($collection=null, $n=null) {
  203. list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
  204. $collection = self::_collection($collection);
  205. if($n === 0) $result = array();
  206. elseif($n === 1 || is_null($n)) $result = array_pop($collection);
  207. else {
  208. $__ = new self;
  209. $result = $__->rest($collection, -$n);
  210. }
  211. return self::_wrap($result);
  212. }
  213. // Return a copy of the array with falsy values removed
  214. public function compact($collection=null) {
  215. list($collection) = self::_wrapArgs(func_get_args(), 1);
  216. $collection = self::_collection($collection);
  217. $__ = new self;
  218. return self::_wrap($__->select($collection, function($val) {
  219. return (bool) $val;
  220. }));
  221. }
  222. // Flattens a multidimensional array
  223. public function flatten($collection=null, $shallow=null) {
  224. list($collection, $shallow) = self::_wrapArgs(func_get_args(), 2);
  225. $collection = self::_collection($collection);
  226. $return = array();
  227. if(count($collection) > 0) {
  228. foreach($collection as $item) {
  229. if(is_array($item)) {
  230. $__ = new self;
  231. $return = array_merge($return, ($shallow) ? $item : $__->flatten($item));
  232. }
  233. else $return[] = $item;
  234. }
  235. }
  236. return self::_wrap($return);
  237. }
  238. // Returns a copy of the array with all instances of val removed
  239. public function without($collection=null, $val=null) {
  240. $args = self::_wrapArgs(func_get_args(), 1);
  241. $collection = $args[0];
  242. $collection = self::_collection($collection);
  243. $num_args = count($args);
  244. if($num_args === 1) return self::_wrap($collection);
  245. if(count($collection) === 0) return self::_wrap($collection);
  246. $__ = new self;
  247. $removes = $__->rest($args);
  248. foreach($removes as $remove) {
  249. $remove_keys = array_keys($collection, $remove, true);
  250. if(count($remove_keys) > 0) {
  251. foreach($remove_keys as $key) {
  252. unset($collection[$key]);
  253. }
  254. }
  255. }
  256. return self::_wrap($collection);
  257. }
  258. // Return an array of the unique values
  259. // uniq alias: unique
  260. public function unique($collection=null, $is_sorted=null, $iterator=null) { return self::uniq($collection, $is_sorted, $iterator); }
  261. public function uniq($collection=null, $is_sorted=null, $iterator=null) {
  262. list($collection, $is_sorted, $iterator) = self::_wrapArgs(func_get_args(), 3);
  263. $collection = self::_collection($collection);
  264. $return = array();
  265. if(count($collection) === 0) return self::_wrap($return);
  266. $calculated = array();
  267. foreach($collection as $item) {
  268. $val = (!is_null($iterator)) ? $iterator($item) : $item;
  269. if(is_bool(array_search($val, $calculated, true))) {
  270. $calculated[] = $val;
  271. $return[] = $item;
  272. }
  273. }
  274. return self::_wrap($return);
  275. }
  276. // Returns an array containing the intersection of all the arrays
  277. public function intersection($array=null) {
  278. $arrays = self::_wrapArgs(func_get_args(), 1);
  279. if(count($arrays) === 1) return self::_wrap($array);
  280. $__ = new self;
  281. $return = $__->first($arrays);
  282. foreach($__->rest($arrays) as $next) {
  283. if(!$__->isArray($next)) $next = str_split((string) $next);
  284. $return = array_intersect($return, $next);
  285. }
  286. return self::_wrap(array_values($return));
  287. }
  288. // Merge together multiple arrays
  289. public function union($array=null) {
  290. $arrays = self::_wrapArgs(func_get_args(), 1);
  291. if(count($arrays) === 1) return self::_wrap($array);
  292. $__ = new self;
  293. return self::_wrap($__->flatten(array_values(array_unique(call_user_func_array('array_merge', $arrays)))));
  294. }
  295. // Get the difference between two arrays
  296. public function difference($array_one=null, $array_two=null) {
  297. $arrays = self::_wrapArgs(func_get_args(), 1);
  298. return self::_wrap(array_values(call_user_func_array('array_diff', $arrays)));
  299. }
  300. // Get the index of the first match
  301. public function indexOf($collection=null, $item=null) {
  302. list($collection, $item) = self::_wrapArgs(func_get_args(), 2);
  303. $collection = self::_collection($collection);
  304. $key = array_search($item, $collection, true);
  305. return self::_wrap((is_bool($key)) ? -1 : $key);
  306. }
  307. // Get the index of the last match
  308. public function lastIndexOf($collection=null, $item=null) {
  309. list($collection, $item) = self::_wrapArgs(func_get_args(), 2);
  310. $collection = self::_collection($collection);
  311. krsort($collection);
  312. $__ = new self;
  313. return self::_wrap($__->indexOf($collection, $item));
  314. }
  315. // Returns an array of integers from start to stop (exclusive) by step
  316. public function range($stop=null) {
  317. $args = self::_wrapArgs(func_get_args(), 1);
  318. $__ = new self;
  319. $args = $__->reject($args, function($val) {
  320. return is_null($val);
  321. });
  322. $num_args = count($args);
  323. switch($num_args) {
  324. case 1:
  325. list($start, $stop, $step) = array(0, $args[0], 1);
  326. break;
  327. case 2:
  328. list($start, $stop, $step) = array($args[0], $args[1], 1);
  329. if($stop < $start) return self::_wrap(array());
  330. break;
  331. default:
  332. list($start, $stop, $step) = array($args[0], $args[1], $args[2]);
  333. if($step > 0 && $step > $stop) return self::_wrap(array($start));
  334. }
  335. $results = range($start, $stop, $step);
  336. // Switch inclusive to exclusive
  337. if($step > 0 && $__->last($results) >= $stop) array_pop($results);
  338. elseif($step < 0 && $__->last($results) <= $stop) array_pop($results);
  339. return self::_wrap($results);
  340. }
  341. // Merges arrays
  342. public function zip($array=null) {
  343. $arrays = self::_wrapArgs(func_get_args());
  344. $num_arrays = count($arrays);
  345. if($num_arrays === 1) return self::_wrap($array);
  346. $__ = new self;
  347. $num_return_arrays = $__->max($__->map($arrays, function($array) {
  348. return count($array);
  349. }));
  350. $return_arrays = $__->range($num_return_arrays);
  351. foreach($return_arrays as $k=>$v) {
  352. if(!is_array($return_arrays[$k])) $return_arrays[$k] = array();
  353. foreach($arrays as $a=>$array) {
  354. $return_arrays[$k][$a] = array_key_exists($k, $array) ? $array[$k] : null;
  355. }
  356. }
  357. return self::_wrap($return_arrays);
  358. }
  359. // Get the max value in the collection
  360. public function max($collection=null, $iterator=null) {
  361. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  362. if(is_null($iterator)) return self::_wrap(max($collection));
  363. $results = array();
  364. foreach($collection as $k=>$item) {
  365. $results[$k] = $iterator($item);
  366. }
  367. arsort($results);
  368. $__ = new self;
  369. $first_key = $__->first(array_keys($results));
  370. return $collection[$first_key];
  371. }
  372. // Get the min value in the collection
  373. public function min($collection=null, $iterator=null) {
  374. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  375. if(is_null($iterator)) return self::_wrap(min($collection));
  376. $results = array();
  377. foreach($collection as $k=>$item) {
  378. $results[$k] = $iterator($item);
  379. }
  380. asort($results);
  381. $__ = new self;
  382. $first_key = $__->first(array_keys($results));
  383. return self::_wrap($collection[$first_key]);
  384. }
  385. // Sort the collection by return values from the iterator
  386. public function sortBy($collection=null, $iterator=null) {
  387. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  388. $results = array();
  389. foreach($collection as $k=>$item) {
  390. $results[$k] = $iterator($item);
  391. }
  392. asort($results);
  393. foreach($results as $k=>$v) {
  394. $results[$k] = $collection[$k];
  395. }
  396. return self::_wrap(array_values($results));
  397. }
  398. // Group the collection by return values from the iterator
  399. public function groupBy($collection=null, $iterator=null) {
  400. list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  401. $result = array();
  402. $collection = (array) $collection;
  403. foreach($collection as $k=>$v) {
  404. $key = (is_callable($iterator)) ? $iterator($v, $k) : $v[$iterator];
  405. if(!array_key_exists($key, $result)) $result[$key] = array();
  406. $result[$key][] = $v;
  407. }
  408. return $result;
  409. }
  410. // Returns the index at which the value should be inserted into the sorted collection
  411. public function sortedIndex($collection=null, $value=null, $iterator=null) {
  412. list($collection, $value, $iterator) = self::_wrapArgs(func_get_args(), 3);
  413. $collection = (array) self::_collection($collection);
  414. $__ = new self;
  415. $calculated_value = (!is_null($iterator)) ? $iterator($value) : $value;
  416. while(count($collection) > 1) {
  417. $midpoint = floor(count($collection) / 2);
  418. $midpoint_values = array_slice($collection, $midpoint, 1);
  419. $midpoint_value = $midpoint_values[0];
  420. $midpoint_calculated_value = (!is_null($iterator)) ? $iterator($midpoint_value) : $midpoint_value;
  421. $collection = ($calculated_value < $midpoint_calculated_value) ? array_slice($collection, 0, $midpoint, true) : array_slice($collection, $midpoint, null, true);
  422. }
  423. $keys = array_keys($collection);
  424. return self::_wrap(current($keys) + 1);
  425. }
  426. // Shuffle the array
  427. public function shuffle($collection=null) {
  428. list($collection) = self::_wrapArgs(func_get_args(), 1);
  429. $collection = (array) self::_collection($collection);
  430. shuffle($collection);
  431. return self::_wrap($collection);
  432. }
  433. // Return the collection as an array
  434. public function toArray($collection=null) {
  435. return (array) $collection;
  436. }
  437. // Get the collection's keys
  438. public function keys($collection=null) {
  439. list($collection) = self::_wrapArgs(func_get_args(), 1);
  440. if(!is_object($collection) && !is_array($collection)) throw new Exception('Invalid object');
  441. return self::_wrap(array_keys((array) $collection));
  442. }
  443. // Get the collection's values
  444. public function values($collection=null) {
  445. list($collection) = self::_wrapArgs(func_get_args(), 1);
  446. return self::_wrap(array_values((array) $collection));
  447. }
  448. // Copy all properties from the source objects into the destination object
  449. public function extend($object=null) {
  450. $args = self::_wrapArgs(func_get_args(), 1);
  451. $num_args = func_num_args();
  452. if($num_args === 1) return $object;
  453. $is_object = is_object($object);
  454. $array = (array) $object;
  455. $__ = new self;
  456. $extensions = $__->rest(func_get_args());
  457. foreach($extensions as $extension) {
  458. $extension = (array) $extension;
  459. $array = array_merge($array, $extension);
  460. }
  461. return self::_wrap(($is_object) ? (object) $array : $array);
  462. }
  463. // Returns the object with any missing values filled in using the defaults.
  464. public function defaults($object=null) {
  465. $args = self::_wrapArgs(func_get_args(), 1);
  466. list($object) = $args;
  467. $num_args = count($args);
  468. if($num_args === 1) return $object;
  469. $is_object = is_object($object);
  470. $array = (array) $object;
  471. $__ = new self;
  472. $extensions = $__->rest($args);
  473. foreach($extensions as $extension) {
  474. $extension = (array) $extension;
  475. $array = array_merge($extension, $array);
  476. }
  477. return self::_wrap(($is_object) ? (object) $array : $array);
  478. }
  479. // Get the names of functions available to the object
  480. // functions alias: methods
  481. public function methods($object=null) { return self::functions($object); }
  482. public function functions($object=null) {
  483. list($object) = self::_wrapArgs(func_get_args(), 1);
  484. return self::_wrap(get_class_methods(get_class($object)));
  485. }
  486. // Returns a shallow copy of the object
  487. public function clon(&$object=null) {
  488. list($object) = self::_wrapArgs(func_get_args(), 1);
  489. $clone = null;
  490. if(is_array($object)) $clone = (array) clone (object) $object;
  491. elseif(!is_object($object)) $clone = $object;
  492. elseif(!$clone) $clone = clone $object;
  493. // shallow copy object
  494. if(is_object($clone) && count($clone) > 0) {
  495. foreach($clone as $k=>$v) {
  496. if(is_array($v) || is_object($v)) $clone->$k =& $object->$k;
  497. }
  498. }
  499. // shallow copy array
  500. elseif(is_array($clone) && count($clone) > 0) {
  501. foreach($clone as $k=>$v) {
  502. if(is_array($v) || is_object($v)) $clone[$k] =& $object[$k];
  503. }
  504. }
  505. return self::_wrap($clone);
  506. }
  507. // Invokes the interceptor on the object, then returns the object
  508. public function tap($object=null, $interceptor=null) {
  509. list($object, $interceptor) = self::_wrapArgs(func_get_args(), 2);
  510. $interceptor($object);
  511. return self::_wrap($object);
  512. }
  513. // Does the given key exist?
  514. public function has($collection=null, $key=null) {
  515. list($collection, $key) = self::_wrapArgs(func_get_args(), 2);
  516. $collection = (array) self::_collection($collection);
  517. return self::_wrap(array_key_exists($key, $collection));
  518. }
  519. // Are these items equal?
  520. public function isEqual($a=null, $b=null) {
  521. list($a, $b) = self::_wrapArgs(func_get_args(), 2);
  522. if(isset($this) && isset($this->_chained) && $this->_chained) $a =& $this;
  523. if($a === $b) return self::_wrap(true);
  524. if(gettype($a) !== gettype($b)) return self::_wrap(false);
  525. if(is_callable($a) !== is_callable($b)) return self::_wrap(false);
  526. if($a == $b) return self::_wrap(true);
  527. // Objects and arrays compared by values
  528. if(is_object($a) || is_array($a)) {
  529. // Do either implement isEqual()?
  530. if(is_object($a) && isset($a->isEqual)) return self::_wrap($a->isEqual($b));
  531. if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
  532. if(is_array($a) && array_key_exists('isEqual', $a)) return self::_wrap($a['isEqual']($b));
  533. if(is_array($b) && array_key_exists('isEqual', $b)) return self::_wrap($b['isEqual']($a));
  534. if(count($a) !== count($b)) return self::_wrap(false);
  535. $__ = new self;
  536. $keys_equal = $__->isEqual($__->keys($a), $__->keys($b));
  537. $values_equal = $__->isEqual($__->values($a), $__->values($b));
  538. return self::_wrap($keys_equal && $values_equal);
  539. }
  540. return self::_wrap(false);
  541. }
  542. // Is this item empty?
  543. public function isEmpty($item=null) {
  544. list($item) = self::_wrapArgs(func_get_args(), 1);
  545. return self::_wrap(is_array($item) || is_object($item)) ? !((bool) count((array) $item)) : (!(bool) $item);
  546. }
  547. // Is this item an object?
  548. public function isObject($item=null) {
  549. list($item) = self::_wrapArgs(func_get_args(), 1);
  550. return self::_wrap(is_object($item));
  551. }
  552. // Is this item an array?
  553. public function isArray($item=null) {
  554. list($item) = self::_wrapArgs(func_get_args(), 1);
  555. return self::_wrap(is_array($item));
  556. }
  557. // Is this item a string?
  558. public function isString($item=null) {
  559. list($item) = self::_wrapArgs(func_get_args(), 1);
  560. return self::_wrap(is_string($item));
  561. }
  562. // Is this item a number?
  563. public function isNumber($item=null) {
  564. list($item) = self::_wrapArgs(func_get_args(), 1);
  565. return self::_wrap((is_int($item) || is_float($item)) && !is_nan($item) && !is_infinite($item));
  566. }
  567. // Is this item a bool?
  568. public function isBoolean($item=null) {
  569. list($item) = self::_wrapArgs(func_get_args(), 1);
  570. return self::_wrap(is_bool($item));
  571. }
  572. // Is this item a function (by type, not by name)?
  573. public function isFunction($item=null) {
  574. list($item) = self::_wrapArgs(func_get_args(), 1);
  575. return self::_wrap(is_object($item) && is_callable($item));
  576. }
  577. // Is this item an instance of DateTime?
  578. public function isDate($item=null) {
  579. list($item) = self::_wrapArgs(func_get_args(), 1);
  580. return self::_wrap(is_object($item) && get_class($item) === 'DateTime');
  581. }
  582. // Is this item a NaN value?
  583. public function isNaN($item=null) {
  584. list($item) = self::_wrapArgs(func_get_args(), 1);
  585. return self::_wrap(is_nan($item));
  586. }
  587. // Returns the same value passed as the argument
  588. public function identity() {
  589. $args = self::_wrapArgs(func_get_args(), 1);
  590. if(is_array($args)) return self::_wrap($args[0]);
  591. return self::_wrap(function($x) {
  592. return $x;
  593. });
  594. }
  595. // Generate a globally unique id, optionally prefixed
  596. public $_uniqueId = -1;
  597. public function uniqueId($prefix=null) {
  598. list($prefix) = self::_wrapArgs(func_get_args(), 1);
  599. $_instance = self::getInstance();
  600. $_instance->_uniqueId++;
  601. return (is_null($prefix)) ? self::_wrap($_instance->_uniqueId) : self::_wrap($prefix . $_instance->_uniqueId);
  602. }
  603. // Invokes the iterator n times
  604. public function times($n=null, $iterator=null) {
  605. list($n, $iterator) = self::_wrapArgs(func_get_args(), 2);
  606. if(is_null($n)) $n = 0;
  607. for($i=0; $i<$n; $i++) $iterator($i);
  608. return self::_wrap(null);
  609. }
  610. // Extend the class with your own functions
  611. private $_mixins = array();
  612. public function mixin($functions=null) {
  613. list($functions) = self::_wrapArgs(func_get_args(), 1);
  614. $mixins =& self::getInstance()->_mixins;
  615. foreach($functions as $name=>$function) {
  616. $mixins[$name] = $function;
  617. }
  618. return self::_wrap(null);
  619. }
  620. // Allows extending methods in static context
  621. public static function __callStatic($name, $arguments) {
  622. $mixins =& self::getInstance()->_mixins;
  623. return call_user_func_array($mixins[$name], $arguments);
  624. }
  625. // Allows extending methods in non-static context
  626. public function __call($name, $arguments) {
  627. $mixins =& self::getInstance()->_mixins;
  628. $arguments = self::_wrapArgs($arguments);
  629. return call_user_func_array($mixins[$name], $arguments);
  630. }
  631. // Temporary PHP open and close tags used within templates
  632. // Allows for normal processing of templates even when
  633. // the developer uses PHP open or close tags for interpolation or evaluation
  634. const TEMPLATE_OPEN_TAG = '760e7dab2836853c63805033e514668301fa9c47';
  635. const TEMPLATE_CLOSE_TAG= 'd228a8fa36bd7db108b01eddfb03a30899987a2b';
  636. const TEMPLATE_DEFAULT_EVALUATE = '/<%([\s\S]+?)%>/';
  637. const TEMPLATE_DEFAULT_INTERPOLATE= '/<%=([\s\S]+?)%>/';
  638. const TEMPLATE_DEFAULT_ESCAPE = '/<%-([\s\S]+?)%>/';
  639. public $_template_settings = array(
  640. 'evaluate' => self::TEMPLATE_DEFAULT_EVALUATE,
  641. 'interpolate' => self::TEMPLATE_DEFAULT_INTERPOLATE,
  642. 'escape' => self::TEMPLATE_DEFAULT_ESCAPE
  643. );
  644. // Set template settings
  645. public function templateSettings($settings=null) {
  646. $_template_settings =& self::getInstance()->_template_settings;
  647. if(is_null($settings)) {
  648. $_template_settings = array(
  649. 'evaluate' => self::TEMPLATE_DEFAULT_EVALUATE,
  650. 'interpolate' => self::TEMPLATE_DEFAULT_INTERPOLATE,
  651. 'escape' => self::TEMPLATE_DEFAULT_ESCAPE
  652. );
  653. return true;
  654. }
  655. foreach($settings as $k=>$v) {
  656. if(!array_key_exists($k, $_template_settings)) continue;
  657. $_template_settings[$k] = $v;
  658. }
  659. return true;
  660. }
  661. // Compile templates into functions that can be evaluated for rendering
  662. public function template($code=null, $context=null) {
  663. list($code, $context) = self::_wrapArgs(func_get_args(), 2);
  664. $class_name = __CLASS__;
  665. $return = self::_wrap(function($context=null) use ($code, $class_name) {
  666. $ts = $class_name::getInstance()->_template_settings;
  667. // Wrap escaped, interpolated, and evaluated blocks inside PHP tags
  668. extract((array) $context);
  669. preg_match_all($ts['escape'], $code, $vars, PREG_SET_ORDER);
  670. if(count($vars) > 0) {
  671. foreach($vars as $var) {
  672. $echo = $class_name::TEMPLATE_OPEN_TAG . ' echo htmlentities(' . trim($var[1]) . '); ' . $class_name::TEMPLATE_CLOSE_TAG;
  673. $code = str_replace($var[0], $echo, $code);
  674. }
  675. }
  676. preg_match_all($ts['interpolate'], $code, $vars, PREG_SET_ORDER);
  677. if(count($vars) > 0) {
  678. foreach($vars as $var) {
  679. $echo = $class_name::TEMPLATE_OPEN_TAG . ' echo ' . trim($var[1]) . '; ' . $class_name::TEMPLATE_CLOSE_TAG;
  680. $code = str_replace($var[0], $echo, $code);
  681. }
  682. }
  683. preg_match_all($ts['evaluate'], $code, $vars, PREG_SET_ORDER);
  684. if(count($vars) > 0) {
  685. foreach($vars as $var) {
  686. $echo = $class_name::TEMPLATE_OPEN_TAG . trim($var[1]) . $class_name::TEMPLATE_CLOSE_TAG;
  687. $code = str_replace($var[0], $echo, $code);
  688. }
  689. }
  690. $code = str_replace($class_name::TEMPLATE_OPEN_TAG, '<?php ', $code);
  691. $code = str_replace($class_name::TEMPLATE_CLOSE_TAG, '?>', $code);
  692. // Use the output buffer to grab the return value
  693. $code = 'ob_start(); extract($context); ?>' . $code . '<?php return ob_get_clean();';
  694. $func = create_function('$context', $code);
  695. return $func((array) $context);
  696. });
  697. // Return function or call function depending on context
  698. return self::_wrap(((isset($this) && isset($this->_wrapped) && $this->_wrapped) || !is_null($context)) ? $return($context) : $return);
  699. }
  700. // Escape
  701. public function escape($item=null) {
  702. list($item) = self::_wrapArgs(func_get_args(), 1);
  703. return self::_wrap(htmlentities($item));
  704. }
  705. // Memoizes a function by caching the computed result.
  706. public $_memoized = array();
  707. public function memoize($function=null, $hashFunction=null) {
  708. list($function, $hashFunction) = self::_wrapArgs(func_get_args(), 2);
  709. $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
  710. return self::_wrap(function() use ($function, &$_instance, $hashFunction) {
  711. // Generate a key based on hashFunction
  712. $args = func_get_args();
  713. if(is_null($hashFunction)) $hashFunction = function($function, $args) {
  714. // Try using var_export to identify the function
  715. return md5(join('_', array(
  716. var_export($function, true),
  717. var_export($args, true)
  718. )));
  719. };
  720. $key = $hashFunction($function, $args);
  721. if(!array_key_exists($key, $_instance->_memoized)) {
  722. $_instance->_memoized[$key] = call_user_func_array($function, $args);
  723. }
  724. return $_instance->_memoized[$key];
  725. });
  726. }
  727. // Throttles a function so that it can only be called once every wait milliseconds
  728. public $_throttled = array();
  729. public function throttle($function=null, $wait=null) {
  730. list($function, $wait) = self::_wrapArgs(func_get_args(), 2);
  731. $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
  732. return self::_wrap(function() use ($function, $wait, &$_instance) {
  733. // Try using var_export to identify the function
  734. $key = md5(join('', array(
  735. var_export($function, true),
  736. $wait
  737. )));
  738. $microtime = microtime(true);
  739. $ready_to_call = (!array_key_exists($key, $_instance->_throttled) || $microtime >= $_instance->_throttled[$key]);
  740. if($ready_to_call) {
  741. $next_callable_time = $microtime + ($wait / 1000);
  742. $_instance->_throttled[$key] = $next_callable_time;
  743. return call_user_func_array($function, func_get_args());
  744. }
  745. });
  746. }
  747. // Creates a version of the function that can only be called once
  748. public $_onced = array();
  749. public function once($function=null) {
  750. list($function) = self::_wrapArgs(func_get_args(), 1);
  751. $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
  752. return self::_wrap(function() use ($function, &$_instance) {
  753. // Try using var_export to identify the function
  754. $key = md5(var_export($function, true));
  755. if(!array_key_exists($key, $_instance->_onced)) {
  756. $_instance->_onced[$key] = call_user_func_array($function, func_get_args());
  757. }
  758. return $_instance->_onced[$key];
  759. });
  760. }
  761. // Wraps the function inside the wrapper function, passing it as the first argument
  762. public function wrap($function=null, $wrapper=null) {
  763. list($function, $wrapper) = self::_wrapArgs(func_get_args(), 2);
  764. return self::_wrap(function() use ($wrapper, $function) {
  765. $args = array_merge(array($function), func_get_args());
  766. return call_user_func_array($wrapper, $args);
  767. });
  768. }
  769. // Returns the composition of the functions
  770. public function compose() {
  771. $functions = self::_wrapArgs(func_get_args(), 1);
  772. return self::_wrap(function() use ($functions) {
  773. $args = func_get_args();
  774. foreach($functions as $function) {
  775. $args[0] = call_user_func_array($function, $args);
  776. }
  777. return $args[0];
  778. });
  779. }
  780. // Creates a version of the function that will only run after being called count times
  781. public $_aftered = array();
  782. public function after($count=null, $function=null) {
  783. list($count, $function) = self::_wrapArgs(func_get_args(), 2);
  784. $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
  785. $key = md5(mt_rand());
  786. $func = function() use ($function, &$_instance, $count, $key) {
  787. if(!array_key_exists($key, $_instance->_aftered)) $_instance->_aftered[$key] = 0;
  788. $_instance->_aftered[$key] += 1;
  789. if($_instance->_aftered[$key] >= $count) return call_user_func_array($function, func_get_args());
  790. };
  791. return self::_wrap(($count) ? $func : $func());
  792. }
  793. // Singleton
  794. private static $_instance;
  795. public function getInstance() {
  796. if(!isset(self::$_instance)) {
  797. $c = __CLASS__;
  798. self::$_instance = new $c;
  799. }
  800. return self::$_instance;
  801. }
  802. // All methods should wrap their returns within _wrap
  803. // because this function understands both OO-style and functional calls
  804. public $_wrapped; // Value passed from one chained method to the next
  805. private function _wrap($val) {
  806. if(isset($this) && isset($this->_chained) && $this->_chained) {
  807. $this->_wrapped = $val;
  808. return $this;
  809. }
  810. return $val;
  811. }
  812. // All methods should get their arguments from _wrapArgs
  813. // because this function understands both OO-style and functional calls
  814. private function _wrapArgs($caller_args, $num_args=null) {
  815. $num_args = (is_null($num_args)) ? count($caller_args) - 1 : $num_args;
  816. $filled_args = array();
  817. if(isset($this) && isset($this->_wrapped)) {
  818. $filled_args[] =& $this->_wrapped;
  819. }
  820. if(count($caller_args) > 0) {
  821. foreach($caller_args as $k=>$v) {
  822. $filled_args[] = $v;
  823. }
  824. }
  825. return array_pad($filled_args, $num_args, null);
  826. }
  827. // Get a collection in a way that supports strings
  828. private function _collection($collection) {
  829. return (!is_array($collection) && !is_object($collection)) ? str_split((string) $collection) : $collection;
  830. }
  831. }