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 &amp; 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

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.