PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/underscore.php

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