1 : <?php
2 : /**
3 : * Library of Utility Functions in the Spirit of Underscore.js
4 : *
5 : * This source file is subject to the MIT license that is bundled
6 : * with this package in the file LICENSE.txt.
7 : *
8 : * @package Underscore.php
9 : * @author Christoph Hochstrasser <christoph.hochstrasser@gmail.com>
10 : * @copyright Copyright (c) Christoph Hochstrasser
11 : * @license MIT License
12 : */
13 :
14 : // Define __() Utility function in global Namespace
15 : namespace
16 : {
17 : if (!function_exists('__')) {
18 : function __($value)
19 1 : {
20 4 : return new \Underscore\Wrapper($value);
21 : }
22 1 : }
23 1 : }
24 :
25 : /** @namespace */
26 : namespace Underscore
27 : {
28 1 : use InvalidArgumentException, ArrayAccess, IteratorAggregate, Countable;
29 :
30 : /**
31 : * Returns a chainable represenation of the given value
32 : *
33 : * @param mixed $value
34 : * @return Collection
35 : */
36 : function chain($value)
37 : {
38 3 : return new Wrapper($value, true);
39 : }
40 :
41 : function from($value)
42 1 : {
43 0 : return chain($value);
44 1 : }
45 1 :
46 1 : function perform($value)
47 : {
48 0 : return chain($value);
49 : }
50 :
51 : class Wrapper implements \ArrayAccess, \IteratorAggregate, \Countable
52 : {
53 : /** @var array */
54 : protected $value;
55 : protected $chain;
56 :
57 : function __construct($value = array(), $chain = false)
58 : {
59 7 : $this->value = $value;
60 7 : $this->chain = $chain;
61 7 : }
62 1 :
63 : /**
64 1 : * Forwards calls to the underscore functions and passes the value
65 : * of the wrapped object as first argument
66 : *
67 : * @param string $method
68 : * @param array $args
69 : * @return Chain
70 : */
71 : function __call($method, array $args)
72 : {
73 7 : array_unshift($args, $this->value);
74 7 : $this->value = call_user_func_array(
75 7 : __NAMESPACE__ . '\\' . $method, $args
76 7 : );
77 6 : return $this->chain ? $this : $this->value;
78 : }
79 :
80 : function range($start, $stop, $step = 1)
81 : {
82 0 : $this->value = range($start, $stop, $step);
83 0 : return $this;
84 : }
85 :
86 : function shift()
87 : {
88 0 : $value = array_shift($this->value);
89 :
90 0 : if ($this->chain) {
91 0 : $this->value = $value;
92 0 : return $this;
93 : }
94 0 : return $value;
95 : }
96 :
97 : function unshift($value)
98 : {
99 1 : array_unshift($this->value, $value);
100 1 : return $this;
101 : }
102 :
103 : function pop()
104 : {
105 1 : $value = array_pop($this->value);
106 :
107 1 : if ($this->chain) {
108 1 : $this->value = $value;
109 2 : return $this;
110 : }
111 0 : return $value;
112 : }
113 :
114 : function push($value)
115 : {
116 0 : $this->value[] = $value;
117 0 : return $this;
118 : }
119 :
120 : function reverse()
121 : {
122 1 : $this->value = array_reverse(toArray($this->value));
123 1 : return $this;
124 : }
125 :
126 : function concat()
127 : {
128 1 : $result = $this->value ?: array();
129 1 : foreach (func_get_args() as $list) {
130 1 : $result = array_merge($result, $list);
131 1 : }
132 1 : $this->value = $result;
133 1 : return $this;
134 : }
135 :
136 : function value()
137 : {
138 1 : return $this->value;
139 : }
140 :
141 : /**
142 : * Implement Countable
143 : *
144 : * @return int
145 : */
146 : function count()
147 : {
148 0 : return count($this->value);
149 : }
150 :
151 : /**
152 : * Implement IteratorAggregate
153 : */
154 : function getIterator()
155 : {
156 0 : return new ArrayIterator($this->value);
157 : }
158 :
159 : /*
160 : * Implement ArrayAccess
161 : */
162 :
163 : function offsetGet($offset)
164 : {
165 0 : return $this->value[$offset];
166 : }
167 :
168 : function offsetSet($offset, $value)
169 : {
170 0 : if (null === $offset)
171 0 : $this->push($value);
172 : else
173 0 : $this->value[$offset] = $value;
174 0 : }
175 :
176 : function offsetExists($offset)
177 : {
178 0 : return isset($this->value[$offset]);
179 : }
180 :
181 : function offsetUnset($offset)
182 : {
183 0 : unset($this->value[$offset]);
184 0 : }
185 : }
186 :
187 : /**
188 : * Does nothing with the value, only passes it to a callback function
189 : * Can be used to inspect chains.
190 : *
191 : * @param mixed $value Value to inspect
192 : * @param callback $callback
193 : * @return mixed The given value
194 : */
195 : function tap($value, $callback)
196 : {
197 0 : if (!is_callable($callback)) {
198 0 : throw new InvalidArgumentException(sprintf(
199 0 : "%s expects a Callback as second argument, %s given",
200 0 : __FUNCTION__, gettype($callback)
201 0 : ));
202 : }
203 0 : call_user_func($callback, $value);
204 0 : return $value;
205 : }
206 :
207 : function each($list, $iterator)
208 : {
209 1 : if (!$list) return;
210 1 : foreach ($list as $key => $value) {
211 1 : call_user_func($iterator, $value, $key, $list);
212 1 : }
213 1 : }
214 :
215 : function map($list, $iterator)
216 : {
217 3 : $return = array();
218 :
219 3 : foreach ($list as $key => $value) {
220 2 : $return[$key] = call_user_func($iterator, $value, $key);
221 2 : }
222 2 : return $return;
223 : }
224 :
225 : function inject($list, $iterator, $memo = 0)
226 : {
227 1 : return reduce($list, $iterator, $memo);
228 : }
229 :
230 : function reduce($list, $iterator, $memo = 0)
231 : {
232 2 : return array_reduce((array) $list, $iterator, $memo);
233 : }
234 :
235 : /**
236 : * @alias detect()
237 : */
238 : function find($list, $iterator)
239 : {
240 0 : return detect($list, $iterator);
241 : }
242 :
243 : /**
244 : * Returns the first element which passes a truth test
245 : *
246 : * @param array $list
247 : * @param callback $iterator
248 : * @return mixed
249 : */
250 : function detect($list, $iterator)
251 : {
252 1 : foreach ($list as $key => $value) {
253 1 : if (true === (bool) call_user_func($iterator, $value, $key)) {
254 1 : return $value;
255 : }
256 1 : }
257 0 : return false;
258 : }
259 :
260 : /**
261 : * Returns all elements which pass the truth test
262 : *
263 : * @param array $list
264 : * @param callback $iterator
265 : * @return array
266 : */
267 : function filter($list, $iterator)
268 : {
269 0 : return select($list, $iterator);
270 : }
271 :
272 : function select($list, $iterator)
273 : {
274 1 : return array_filter((array) $list, $iterator);
275 : }
276 :
277 : function reject($list, $iterator)
278 : {
279 1 : $return = array();
280 :
281 1 : foreach ($list as $key => $value) {
282 1 : if (true !== (bool) call_user_func($iterator, $value, $key)) {
283 1 : $return[$key] = $value;
284 1 : }
285 1 : }
286 1 : return $return;
287 : }
288 :
289 : /**
290 : * @alias all
291 : */
292 : function every($list, $iterator)
293 : {
294 0 : return all($list, $iterator);
295 : }
296 :
297 : /**
298 : * Returns true if all elements pass a truth test
299 : */
300 : function all($list, $iterator)
301 : {
302 0 : $valid = true;
303 :
304 0 : foreach ($list as $key => $value) {
305 0 : $valid = (bool) call_user_func($iterator, $value, $key);
306 0 : }
307 0 : return $valid;
308 : }
309 :
310 : /**
311 : * @alias any()
312 : */
313 : function some($list, $iterator)
314 : {
315 0 : return any($list, $iterator);
316 : }
317 :
318 : function any($list, $iterator)
319 : {
320 0 : return detect($list, $iterator) ? true : false;
321 : }
322 :
323 : /**
324 : * @alias contains
325 : */
326 : function includes($list, $value)
327 : {
328 0 : return contains($list, $value);
329 : }
330 :
331 : function contains($list, $value)
332 : {
333 1 : if (is_array($list)) {
334 1 : return indexOf($list, $value) >= 0 ? true : false;
335 : }
336 0 : foreach ($list as $value) {
337 0 : if ($v === $value) {
338 0 : return true;
339 : }
340 0 : }
341 0 : return false;
342 : }
343 :
344 : function invoke($list, $method/*, $arg,... */)
345 : {
346 0 : $args = array_slice(func_get_args(), 2);
347 :
348 0 : foreach ($list as $object) {
349 0 : if (is_callable($object, $method)) {
350 0 : call_user_func_array(array($object, $method), $args);
351 0 : }
352 0 : }
353 0 : return $list;
354 : }
355 :
356 : /**
357 : * Iterates over the list of objects, reads the given property from each Object
358 : * and returns the collected values as list
359 : *
360 : * @param array $list List of Objects
361 : * @param string $property Name of the Object Property
362 : * @return array
363 : */
364 : function pluck($list, $property)
365 : {
366 0 : $values = array();
367 0 : foreach ($list as $object) {
368 0 : if (empty($object->{$property})) {
369 0 : $value = null;
370 0 : } else {
371 0 : $value = $object->{$property};
372 : }
373 0 : $values[] = $value;
374 0 : }
375 0 : return $value;
376 : }
377 :
378 : function size($list)
379 : {
380 0 : if ($list instanceof \Traversable and !$list instanceof \Countable) {
381 0 : return iterator_count($list);
382 : }
383 0 : return count($list);
384 : }
385 :
386 : function toArray($iterable)
387 : {
388 1 : if (!$iterable) return array();
389 :
390 1 : if (is_callable(array($iterable, "toArray")))
391 1 : return $iterable->toArray();
392 :
393 1 : if (is_array($iterable)) return $iterable;
394 :
395 0 : return (array) $iterable;
396 : }
397 :
398 : /**
399 : * Splits the string on spaces and returns the parts
400 : *
401 : * @param string $string
402 : * @return array
403 : */
404 : function w($string)
405 : {
406 0 : return explode(" ", (string) $string);
407 : }
408 :
409 : /**
410 : * Deletes the given key from the array and returns his value
411 : *
412 : * @param array $array
413 : * @param mixed $key Key to search for
414 : * @return mixed Value of the given key, NULL if key was not found in array
415 : */
416 : function deleteKey(&$array, $key)
417 : {
418 0 : if (!isset($array[$key])) {
419 0 : return null;
420 : }
421 0 : $value = $array[$key];
422 0 : unset($array[$key]);
423 0 : return $value;
424 : }
425 :
426 : /**
427 : * Searches the given value in the array, unsets the found offset
428 : * and returns the value
429 : *
430 : * @param array $array
431 : * @param mixed $value Value to search for
432 : * @return mixed The value or NULL if the value was not found
433 : */
434 : function delete(&$array, $value)
435 : {
436 0 : $offset = array_search($value, (array) $array);
437 0 : if (false === $offset) {
438 0 : return null;
439 : }
440 0 : unset($array[$offset]);
441 0 : return $value;
442 : }
443 :
444 : /**
445 : * Returns the array without all falsy values
446 : *
447 : * @param array $array
448 : * @return array
449 : */
450 : function compact($array)
451 : {
452 1 : return array_filter((array) $array);
453 : }
454 :
455 : function flatten($array)
456 : {
457 2 : $result = array();
458 2 : foreach ($array as $key => &$value) {
459 2 : if (is_array($value))
460 2 : $result = array_merge($result, flatten($value));
461 : else
462 2 : $result[] = $value;
463 2 : }
464 2 : return $result;
465 : }
466 :
467 : /**
468 : * Returns a copy of the array with all occurences of $value removed
469 : *
470 : * @param array $array
471 : * @param mixed $value,...
472 : * @return array
473 : */
474 : function without($array/*, $value,... */)
475 : {
476 2 : $return = array();
477 2 : $values = array_slice(func_get_args(), 1);
478 :
479 2 : foreach ($array as $key => $v) {
480 2 : if (!in_array($v, $values, true)) {
481 2 : $return[$key] = $v;
482 2 : }
483 2 : }
484 2 : return $return;
485 : }
486 :
487 : /**
488 : * Returns a duplicate free version of the array
489 : *
490 : * @param array $array
491 : * @return array
492 : */
493 : function uniq($list, $sorted = false)
494 : {
495 2 : return array_unique((array) $list, $sorted ? false : SORT_REGULAR);
496 : }
497 :
498 : function zip(/* $array,... */)
499 : {
500 0 : return array();
501 : }
502 :
503 : /**
504 : * Searches the value in the array and returns its position
505 : *
506 : * @param array $array
507 : * @param mixed $value
508 : * @return mixed Index of the element or -1 if it was not found
509 : */
510 : function indexOf($array, $value)
511 : {
512 2 : if (null === $array)
513 2 : return -1;
514 :
515 2 : $index = array_search($value, (array) $array);
516 2 : return $index ?: -1;
517 : }
518 :
519 : /**
520 : * Returns the intersection of all given arrays
521 : *
522 : * @see \array_intersect
523 : * @return array
524 : */
525 : function intersect(/* $array,... */)
526 : {
527 : // It may look silly, but call_user_func is still dead slow as of PHP 5.3
528 2 : if (2 === func_num_args()) {
529 2 : list($a1, $a2) = func_get_args();
530 2 : return array_intersect($a1, $a2);
531 : }
532 :
533 0 : $arrays = func_get_args();
534 :
535 0 : foreach ($arrays as &$array) {
536 0 : $array = (array) $array;
537 0 : }
538 :
539 0 : return call_user_func_array("array_intersect", $arrays);
540 : }
541 :
542 : /**
543 : * @alias first()
544 : */
545 : function head($list, $length)
546 : {
547 0 : return first($list, $length);
548 : }
549 :
550 : /**
551 : * Returns the first element of the array
552 : *
553 : * @return mixed
554 : */
555 : function first($list, $length = 1)
556 : {
557 3 : $list = (array) $list;
558 3 : return $length === 1 ? array_shift($list) : array_slice($list, 0, $length);
559 : }
560 :
561 : /**
562 : * Returns the last element of the array
563 : *
564 : * @return mixed
565 : */
566 : function last($list)
567 : {
568 1 : $list = (array) $list;
569 1 : return array_pop($list);
570 : }
571 :
572 : /**
573 : * @alias rest()
574 : */
575 : function tail($list, $index = null)
576 : {
577 0 : return rest($list, $index);
578 : }
579 :
580 : function rest($list, $index = null)
581 : {
582 2 : $list = (array) $list;
583 2 : return array_slice($list, (null === $index ? 1 : $index));
584 : }
585 :
586 : /*
587 : * Function functions
588 : */
589 :
590 : /**
591 : * Calls a supplied callback {n} times
592 : *
593 : * @param int $number
594 : * @param callback $callback
595 : * @param mixed $arg,...
596 : * @return mixed Returns the return value of the last call
597 : */
598 : function times($number, $callback/*, $arg,... */)
599 : {
600 0 : $args = array_slice(func_get_args(), 2);
601 :
602 0 : for ($i = 0; $i < $number; $i++) {
603 0 : $return = call_user_func_array($callback, $args);
604 0 : }
605 0 : return $return;
606 : }
607 :
608 : /*
609 : * Function Functions
610 : * ==================
611 : */
612 :
613 : /**
614 : * Returns an identity function
615 : *
616 : * An identity function is a function which returns it's passed argument unmodified,
617 : * which is useful for default loop callbacks
618 : *
619 : * @return callback
620 : */
621 : function identity()
622 : {
623 0 : return function($k) { return $k; };
624 : }
625 :
626 : /**
627 : * Wrap a function in another function and avoid a recursion by passing
628 : * the wrapped function as argument to the wrapper
629 : *
630 : * @param callback $fn The function to wrap
631 : * @param callback $wrapper A wrapper function, receives the wrapped function as
632 : * first argument and the arguments passed to the wrapped
633 : * function as subsequent arguments
634 : * @return Closure
635 : */
636 : function wrap($fn, $wrapper)
637 : {
638 : // Unify calling of the wrapped function
639 1 : if (is_array($fn)) {
640 0 : $original = function() use ($fn) {
641 0 : return call_user_func_array($fn, func_get_args());
642 0 : };
643 0 : } else {
644 1 : $original = $fn;
645 : }
646 :
647 1 : $wrapped = function() use ($original, $wrapper) {
648 1 : $args = func_get_args();
649 1 : array_unshift($args, $original);
650 1 : return call_user_func_array($wrapper, $args);
651 1 : };
652 :
653 1 : return $wrapped;
654 : }
655 :
656 : /**
657 : * Allows the supplied function to be called at most once
658 : * All subsequent calls will return the first call's return value
659 : *
660 : * @param callback $fn
661 : * @return mixed
662 : */
663 : function once($fn)
664 : {
665 1 : return function() use ($fn) {
666 1 : static $called = false;
667 1 : static $returnValue;
668 :
669 1 : if ($called) {
670 1 : return $returnValue;
671 : }
672 1 : $called = true;
673 1 : return $returnValue = call_user_func_array($fn, func_get_args());
674 1 : };
675 : }
676 :
677 : /**
678 : * Calls the supplied function after $count calls
679 : *
680 : * @param int $count
681 : * @param callback $fn
682 : * @return mixed
683 : */
684 : function after($count, $fn)
685 : {
686 1 : return function() use ($count, $fn) {
687 1 : static $calls = 0;
688 1 : static $returnValue;
689 :
690 1 : $calls++;
691 :
692 1 : if ($calls == $count) {
693 1 : return $returnValue = call_user_func_array($fn, func_get_args());
694 : }
695 1 : return $returnValue;
696 1 : };
697 : }
698 :
699 : /**
700 : * Caches the result of calls with the same arguments
701 : *
702 : * @param callback $fn
703 : * @return Closure
704 : */
705 : function memoize($fn, $hashFunction = null)
706 : {
707 1 : return function() use ($fn, $hashFunction) {
708 1 : static $results = array();
709 :
710 1 : $args = func_get_args();
711 :
712 1 : $hash = empty($hashFunction)
713 1 : ? md5(join($args, ",")) : call_user_func($hashFunction, $args);
714 :
715 1 : if (empty($results[$hash])) {
716 1 : $results[$hash] = call_user_func_array($fn, $args);
717 1 : }
718 1 : return $results[$hash];
719 1 : };
720 : }
721 :
722 : /**
723 : * Prefills the arguments of a given function
724 : *
725 : * @param callback $fn Function to curry
726 : * @param mixed $value,... Arguments for currying the function
727 : * @return Closure
728 : */
729 : function curry($fn/*, $arg,... */)
730 : {
731 0 : $curry = array_slice(func_get_args(), 1);
732 :
733 0 : return function() use ($fn, $curry) {
734 0 : $args = array_merge($curry, func_get_args());
735 0 : return call_user_func_array($fn, $args);
736 0 : };
737 : }
738 :
739 : /**
740 : * Composes multiple callback functions into one by passing each function's
741 : * return value as argument into the next function. The arguments passed to
742 : * the composed function get passed to the first (most inner) function.
743 : *
744 : * Equals to i(f, g, h) = h(g(f(x)))
745 : *
746 : * @param callback $fn,... Functions to compose
747 : * @return Closure
748 : */
749 : function compose(/* $fn,... */)
750 : {
751 1 : $fns = func_get_args();
752 :
753 1 : return function() use ($fns) {
754 1 : $input = func_get_args();
755 1 : foreach ($fns as $fn) {
756 1 : $returnValue = call_user_func_array($fn, $input);
757 1 : $input = array($returnValue);
758 1 : }
759 1 : return $returnValue;
760 1 : };
761 : }
762 :
763 : /**
764 : * Camelizes a dash or underscore separated string
765 : *
766 : * @param string $string
767 : * @param bool $pascalCase By default the first letter is uppercase
768 : * @return string
769 : */
770 : function camelize($string, $pascalCase = true)
771 : {
772 0 : $string = str_replace(array("-", "_"), " ", $string);
773 0 : $string = ucwords($string);
774 0 : $string = str_replace(" ", null, $string);
775 :
776 0 : if (!$pascalCase) {
777 0 : return lcfirst($string);
778 : }
779 0 : return $string;
780 : }
781 : }
782 :
|