Underscore.php
Underscore.php
by brianhaveri
Download
You can download this project in either
zip or
tar formats.
Single Underscore vs. Double Underscore
In many PHP installations, _()
already exists as an alias to gettext()
. The previous declaration of _
in PHP forces Underscore.php to use another name. For consistency and memorability, __
has been chosen for both the function and class name.
Object-Oriented and Static Styles
Underscore.php works in both object-oriented and static styles. The following lines are identical ways to double a list of numbers.
__::map(array(1, 2, 3), function($n) { return $n * 2; });
__(array(1, 2, 3))->map(function($n) { return $n * 2; });
Collections (Arrays or Objects)
each __::each(collection, iterator)
Iterates over the collection and yield each in turn to the iterator function. Arguments passed to iterator are (value, key, collection). Unlike Underscore.js, context is passed using PHP's use
statement. Underscore.php does not contain the forEach alias because 'foreach' is a reserved keyword in PHP.
__::each(array(1, 2, 3), function($num) { echo $num . ','; }); // 1,2,3,
$multiplier = 2;
__::each(array(1, 2, 3), function($num, $index) use ($multiplier) {
echo $index . '=' . ($num * $multiplier) . ',';
});
// 0=2,1=4,2=6,
map __::map(collection, iterator)
Alias: collect
Returns an array of values by mapping each in collection through the iterator. Arguments passed to iterator are (value, key, collection). Unlike Underscore.js, context is passed using PHP's use
statement.
__::map(array(1, 2, 3), function($num) { return $num * 3; }); // array(3, 6, 9)
__::map(array('one'=>1, 'two'=>2, 'three'=>3), function($num, $key) {
return $num * 3;
});
// array(3, 6, 9);
reduce __::reduce(collection, iterator, memo)
Aliases: inject, foldl
Reduce the collection into a single value. Memo is the initial state of the reduction, updated by the return value of the iterator. Unlike Underscore.js, context is passed using PHP's use
statement.
__::reduce(array(1, 2, 3), function($memo, $num) { return $memo + $num; }, 0); // 6
reduceRight __::reduceRight(collection, iterator, memo)
Alias: foldr
Right-associative version of reduce.
$list = array(array(0, 1), array(2, 3), array(4, 5));
$flat = __::reduceRight($list, function($a, $b) { return array_merge($a, $b); }, array());
// array(4, 5, 2, 3, 0, 1)
find __::find(collection, iterator)
Alias: detect
Return the value of the first item in the collection that passes the truth test (iterator).
__::find(array(1, 2, 3, 4), function($num) { return $num % 2 === 0; }); // 2
filter __::filter(collection, iterator)
Alias: select
Return the values in the collection that pass the truth test (iterator).
__::filter(array(1, 2, 3, 4), function($num) { return $num % 2 === 0; }); // array(2, 4)
reject __::reject(collection, iterator)
Return an array where the items failing the truth test (iterator) are removed.
__::reject(array(1, 2, 3, 4), function($num) { return $num % 2 === 0; }); // array(1, 3)
all __::all(collection, iterator)
Returns true if all values in the collection pass the truth test (iterator).
__::all(array(1, 2, 3, 4), function($num) { return $num % 2 === 0; }); // false
__::all(array(1, 2, 3, 4), function($num) { return $num < 5; }); // true
any __::any(collection, iterator)
Returns true if any values in the collection pass the truth test (iterator).
__::any(array(1, 2, 3, 4), function($num) { return $num % 2 === 0; }); // true
__::any(array(1, 2, 3, 4), function($num) { return $num === 5; }); // false
includ __::includ(collection, value)
Alias: contains
Returns true if value is found in the collection using === to test equality. This function is called 'include' in Underscore.js, but was renamed to 'includ' in Underscore.php because 'include' is a reserved keyword in PHP.
__::includ(array(1, 2, 3), 3); // true
invoke __::invoke(collection, functionName)
Returns a copy of the collection after running functionName across all elements.
__::invoke(array(' foo', ' bar '), 'trim'); // array('foo', 'bar')
pluck __::pluck(collection, propertyName)
Extract an array of property values
$stooges = array(
array('name'=>'moe', 'age'=>40),
array('name'=>'larry', 'age'=>50),
array('name'=>'curly', 'age'=>60)
);
__::pluck($stooges, 'name'); // array('moe', 'larry', 'curly')
max __::max(collection, [iterator])
Returns the maximum value from the collection. If passed an iterator, max will return max value returned by the iterator. Unlike Underscore.js, context is passed using PHP's use
statement.
$stooges = array(
array('name'=>'moe', 'age'=>40),
array('name'=>'larry', 'age'=>50),
array('name'=>'curly', 'age'=>60)
);
__::max($stooges, function($stooge) { return $stooge['age']; });
// array('name'=>'curly', 'age'=>60)
min __::min(collection, [iterator])
Returns the minimum value from the collection. If passed an iterator, min will return min value returned by the iterator. Unlike Underscore.js, context is passed using PHP's use
statement.
$stooges = array(
array('name'=>'moe', 'age'=>40),
array('name'=>'larry', 'age'=>50),
array('name'=>'curly', 'age'=>60)
);
__::min($stooges, function($stooge) { return $stooge['age']; });
// array('name'=>'moe', 'age'=>40)
groupBy __::groupBy(collection, iterator)
Group values by their return value when passed through the iterator. If iterator is a string, the result will be grouped by that property.
__::groupBy(array(1, 2, 3, 4, 5), function($n) { return $n % 2; });
// array(0=>array(2, 4), 1=>array(1, 3, 5))
$values = array(
array('name'=>'Apple', 'grp'=>'a'),
array('name'=>'Bacon', 'grp'=>'b'),
array('name'=>'Avocado', 'grp'=>'a')
);
__::groupBy($values, 'grp');
//array(
// 'a'=>array(
// array('name'=>'Apple', 'grp'=>'a'),
// array('name'=>'Avocado', 'grp'=>'a')
// ),
// 'b'=>array(
// array('name'=>'Bacon', 'grp'=>'b')
// )
//);
sortBy __::sortBy(collection, iterator)
Returns an array sorted in ascending order based on the iterator results. If passed an iterator, min will return min value returned by the iterator. Unlike Underscore.js, context is passed using PHP's use
statement.
__::sortBy(array(1, 2, 3), function($n) { return -$n; }); // array(3, 2, 1)
sortedIndex __::sortedIndex(collection, value, [iterator])
Returns the index at which the value should be inserted into the sorted collection.
__::sortedIndex(array(10, 20, 30, 40), 35); // 3
shuffle __::shuffle(collection)
Returns a shuffled copy of the collection.
__::shuffle(array(10, 20, 30, 40)); // 30, 20, 40, 10
toArray __::toArray(collection)
Converts the collection into an array.
$stooge = new StdClass;
$stooge->name = 'moe';
$stooge->age = 40;
__::toArray($stooge); // array('name'=>'moe', 'age'=>40)
size __::size(collection)
Returns the number of values in the collection.
$stooge = new StdClass;
$stooge->name = 'moe';
$stooge->age = 40;
__::size($stooge); // 2
Arrays
first __::first(array, [n])
Alias: head
Get the first element of an array. Passing n returns the first n elements.
__::first(array(5, 4, 3, 2, 1)); // 5
__::first(array(5, 4, 3, 2, 1), 3); // array(5, 4, 3)
initial __::initial(array, [n])
Get everything but the last array element. Passing n excludes the last n elements.
__::initial(array(5, 4, 3, 2, 1)); // array(5, 4, 3, 2)
__::initial(array(5, 4, 3, 2, 1), 3); // array(5, 4)
rest __::rest(array, [index])
Alias: tail
Get the rest of the array elements. Passing an index returns from that index onward.
__::rest(array(5, 4, 3, 2, 1)); // array(4, 3, 2, 1)
last __::last(array, [n])
Get the last element of an array. Passing n returns the last n elements.
__::last(array(5, 4, 3, 2, 1)); // 1
__::last(array(5, 4, 3, 2, 1), 2); // array(2, 1)
compact __::compact(array)
Returns a copy of the array with falsy values removed
__::compact(array(false, true, 'a', 0, 1, '')); // array(true, 'a', 1)
flatten __::flatten(array, [shallow])
Flattens a multidimensional array. If you pass shallow, the array will only be flattened a single level.
__::flatten(array(1, array(2), array(3, array(array(array(4))))));
// array(1, 2, 3, 4)
__::flatten(array(1, array(2), array(3, array(array(array(4))))), true);
// array(1, 2, 3, array(array(4)))
without __::without(array, [*values])
Returns a copy of the array with all instances of values removed. === is used for equality testing. Keys are maintained.
__::without(array(5, 4, 3, 2, 1), 3, 2); // array(5, 4, 4=>1)
uniq __::uniq(array, [isSorted [, iterator]])
Alias: unique
Returns a copy of the array containing no duplicate values. Unlike Underscore.js, passing isSorted does not currently affect the performance of uniq
. You can optionally compute uniqueness by passing an iterator function.
__::uniq(array(2, 2, 4, 4, 4, 1, 1, 1)); // array(2, 4, 1)
union __::union(*arrays)
Returns an array containing the unique items in one or more of the arrays.
$arr1 = array(1, 2, 3);
$arr2 = array(101, 2, 1, 10);
$arr3 = array(2, 1);
__::union($arr1, $arr2, $arr3); // array(1, 2, 3, 101, 10)
intersection __::intersection(*arrays)
Returns an array containing the intersection of all the arrays. Each value in the resulting array exists in all arrays.
$arr1 = array(0, 1, 2, 3);
$arr2 = array(1, 2, 3, 4);
$arr3 = array(2, 3, 4, 5);
__::intersection($arr1, $arr2, $arr3); // array(2, 3)
difference __::difference(array, *others)
Returns an array containing the items existing in one array, but not the other.
__::difference(array(1, 2, 3, 4, 5), array(5, 2, 10)); // array(1, 3, 4)
zip __::zip(*arrays)
Merges arrays
$names = array('moe', 'larry', 'curly');
$ages = array(30, 40, 50);
$leaders = array(true, false, false);
__::zip($names, $ages, $leaders);
// array(
// array('moe', 30, true),
// array('larry', 40, false),
// array('curly', 50, false)
// )
indexOf __::indexOf(array, value)
Returns the index of the first match. Returns -1 if no match is found. Unlike Underscore.js, Underscore.php does not take a second isSorted parameter.
__::indexOf(array(1, 2, 3, 2, 2), 2); // 1
lastIndexOf __::lastIndexOf(array, value)
Returns the index of the last match. Returns -1 if no match is found.
__::lastIndexOf(array(1, 2, 3, 2, 2), 2); // 4
range __::range([start], stop, [step])
Returns an array of integers from start to stop (exclusive) by step. Defaults: start=0, step=1.
__::range(10); // array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
__::range(1, 5); // array(1, 2, 3, 4)
__::range(0, 30, 5); // array(0, 5, 10, 15, 20, 25)
__::range(0, -5, -1); // array(0, -1, -2, -3, -4)
__::range(0); // array()
Functions
memoize __::memoize(function, [hashFunction])
Memoizes a function by caching the computed result. Useful for computationally expensive functions. Optionally, pass a hashFunction to calculate the key for the cached value.
$fibonacci = function($n) use (&$fibonacci) {
return $n < 2 ? $n : $fibonacci($n - 1) + $fibonacci($n - 2);
};
$fastFibonacci = __::memoize($fibonacci);
throttle __::throttle(function, wait)
Throttles a function so that it can only be called once every wait milliseconds.
$func = function() { return 'x'; }
__::throttle($func);
once __::once(function)
Creates a version of the function that can only be called once.
$num = 0;
$increment = __::once(function() use (&$num) { return $num++; });
$increment();
$increment();
echo $num; // 1
after __::after(count, function)
Creates a version of the function that will only run after being called count times.
$func = __::after(3, function() { return 'x'; });
$func(); //
$func(); //
$func(); // 'x'
wrap __::wrap(function, wrapper)
Wraps the function inside the wrapper function, passing it as the first argument. Lets the wrapper execute code before and/or after the function runs.
$hello = function($name) { return 'hello: ' . $name; };
$hi = __::wrap($hello, function($func) {
return 'before, ' . $func('moe') . ', after';
});
$hi(); // 'before, hello: moe, after'
compose __::compose(*functions)
Returns the composition of the functions, where the return value is passed to the following function.
$greet = function($name) { return 'hi: ' . $name; };
$exclaim = function($statement) { return $statement . '!'; };
$welcome = __::compose($exclaim, $greet);
$welcome('moe'); // 'hi: moe!'
Objects
keys __::keys(object)
Get the keys
__::keys((object) array('name'=>'moe', 'age'=>40)); // array('name', 'age')
values __::values(object)
Get the values
__::values((object) array('name'=>'moe', 'age'=>40)); // array('moe', 40)
functions __::functions(object)
Alias: methods
Get the names of functions available to the object
class Stooge {
public function getName() { return 'moe'; }
public function getAge() { return 40; }
}
$stooge = new Stooge;
__::functions($stooge); // array('getName', 'getAge')
extend __::extend(destination, *sources)
Copy all properties from the source objects into the destination object. Copying happens in order, so rightmost sources have override power.
__::extend((object) array('name'=>'moe'), (object) array('age'=>50));
// (object) array('name'=>'moe', 'age'=>50)
defaults __::defaults(object, *defaults)
Returns the object with any missing values filled in using the defaults. Once a default is applied for a given property, it will not be overridden by following defaults.
$food = (object) array('dairy'=>'cheese');
$defaults = (object) array('meat'=>'bacon');
__::defaults($food, $defaults); // (object) array('dairy'=>'cheese', 'meat'=>'bacon');
clon __::clon(object)
Returns a shallow copy of the object. This function is called 'clone' in Underscore.js, but was renamed to 'clon' in Underscore.php because 'clone' is a reserved keyword in PHP.
$stooge = (object) array('name'=>'moe');
__::clon($stooge); // (object) array('name'=>'moe');
tap __::tap(object, interceptor)
Invokes the interceptor on the object, then returns the object. Useful for performing intermediary operations on the object.
$interceptor = function($obj) { return $obj * 2; };
__::chain(array(1, 2, 3))->max()
->tap($interceptor)
->value(); // 6
has __::has(object, key)
Does the object have this key?
__::has((object) array('a'=>1, 'b'=>2, 'c'=>3), 'b'); // true
isEqual __::isEqual(object, other)
Are these items equal? Uses === equality testing. Objects tested using values.
$stooge = (object) array('name'=>'moe');
$clon = __::clon($stooge);
$stooge === $clon; // false
__::isEqual($stooge, $clon); // true
isEmpty __::isEmpty(object)
Returns true if the object contains no values.
$stooge = (object) array('name'=>'moe');
__::isEmpty($stooge); // false
__::isEmpty(new StdClass); // true
__::isEmpty((object) array()); // true
isObject __::isObject(object)
Returns true if passed an object.
__::isObject((object) array(1, 2)); // true
__::isObject(new StdClass); // true
isArray __::isArray(object)
Returns true if passed an array.
__::isArray(array(1, 2)); // true
__::isArray((object) array(1, 2)); // false
isFunction __::isFunction(object)
Returns true if passed a function.
__::isFunction(function() {}); // true
__::isFunction('trim'); // false
isString __::isString(object)
Returns true if passed a string.
__::isString('moe'); // true
__::isString(''); // true
isNumber __::isNumber(object)
Returns true if passed a number.
__::isNumber(1); // true
__::isNumber(2.5); // true
__::isNumber('5'); // false
isBoolean __::isBoolean(object)
Returns true if passed a boolean.
__::isBoolean(null); // false
__::isBoolean(true); // true
__::isBoolean(0); // false
isDate __::isDate(object)
Returns true if passed a DateTime object
__::isDate(null); // false
__::isDate('2011-06-09 01:02:03'); // false
__::isDate(new DateTime); // true
isNaN __::isNaN(object)
Returns true if value is NaN
__::isNaN(null); // false
__::isNaN(acos(8)); // true
isNull __::isNull(object)
Returns true if value is null
__::isNull(null); // true
__::isNull(false); // false
Utility
identity __::identity(value)
Returns the same value passed as the argument
$moe = array('name'=>'moe');
$moe === __::identity($moe); // true
times __::times(n, iterator)
Invokes the iterator function n times.
__::times(3, function() { echo 'a'; }); // 'aaa'
mixin __::mixin(array)
Extend Underscore.php with your own functions.
__::mixin(array(
'capitalize'=> function($string) { return ucwords($string); },
'yell' => function($string) { return strtoupper($string); }
));
__::capitalize('moe'); // 'Moe'
__::yell('moe'); // 'MOE'
uniqueId __::uniqueId([prefix])
Generate a globally unique id.
__::uniqueId(); // 0
__::uniqueId('stooge_'); // 'stooge_1'
__::uniqueId(); // 2
escape __::escape(html)
Escapes the string.
__::escape('Curly, Larry & Moe'); // 'Curly, Larry & Moe'
template __::template(templateString, [context])
Compile templates into functions that can be evaluated for rendering. Templates can interpolate variables and execute arbitrary PHP code.
$compiled = __::template('hello: <%= $name %>');
$compiled(array('name'=>'moe')); // 'hello: moe'
$list = '<% __::each($people, function($name) { %> <li><%= $name %></li> <% }); %>';
__::template($list, array('people'=>array('moe', 'curly', 'larry')));
// '<li>moe</li><li>curly</li><li>larry</li>'
Single vs. double quotes
Note: if your template strings include variables, wrap your template strings in single quotes, not double quotes. Wrapping in double quotes will cause your variables to be interpolated prior to entering the template function.
// Correct
$compiled = __::template('hello: <%= $name %>');
// Incorrect
$compiled = __::template("hello: <%= $name %>");
Custom delimiters
You can set custom delimiters (for instance, Mustache style) by calling __::templateSettings()
and passing interpolate and/or evaluate values:
// Mustache style
__::templateSettings(array(
'interpolate' => '/\{\{(.+?)\}\}/'
));
$mustache = __::template('Hello {{$planet}}!');
$mustache(array('planet'=>'World')); // "Hello World!"
Chaining
chain __::chain(item)
Returns a wrapped object. Methods will return the object until you call value()
// filter and reverse the numbers
$numbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
$result = __::chain($numbers)->select(function($n) { return $n < 5; })
->reject(function($n) { return $n === 3; })
->sortBy(function($n) { return -$n; })
->value(); // array(4, 2, 1)
value __(obj)->value()
Extracts the value of a wrapped object.
__(array(1, 2, 3))->value(); // array(1, 2, 3)
Change Log
Underscore.php version numbers have corresponding Underscore.js releases
1.3.1 — Jan. 31, 2012
Added has()
Added collect()
as an alias to map()
1.2.4 — Jan. 8, 2012
Jumping from 1.0 to 1.2.4 to match Underscore.js.
1.0 — Aug. 6, 2011
Initial release
Author
Brian Haveri GitHub, @brianhaveri
Credit
Thanks to Jeremy Ashkenas and all contributors to Underscore.js.