Getting Started with PHP Underscore

Tweet

If you’ve ever used the Backbone framework for JavaScript, you’ll already be familiar with Underscore. Indeed, it’s become incredibly useful for JavaScript developers in general. But did you know that it’s been ported to PHP?

In this article I’ll take a look at Underscore, what it can do, and provide some examples of where it might be useful.

What is Underscore?

Underscore describes itself as a “utility belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It’s the tie to go along with jQuery’s tux, and Backbone.js’s suspenders.”

Most notably, Underscore provides a bunch of utilities for working with collections and arrays, some for working with objects, basic templating functionality and a number of other useful functions.

The functions which operate on collections and arrays can be particularly useful when dealing with JSON, which makes it great for handling responses from web services.

Installation / Download

The easiest way to download the library is using Composer:

{
    "require": {
        "underscore/underscore.php": "dev-master"
    }   
}

Alternatively, you can download it manually, or clone it from Github – you need only require one file, underscore.php.

Syntax of PHP Underscore

In the original JavaScript library, all Underscore’s functions are prefixed with an underscore and a dot; e.g. _.each, _.map, _.reduce. In PHP, the underscore is generally reserved as an alias of gettext() (for translating strings), so instead it uses a double-underscore.

All the functions that make up the library are available as static methods of a class called __ – i.e., a double-underscore.

So, if we wish to map the JavaScript functions to their PHP equivalent:

JavaScript          PHP
_.each              __::each
_.map               __::map
_.reduce            __::reduce

…and so on.

You can also use it in an object-oriented fashion;

__(array(1, 2, 3))->map(function($n) { return $n * 2; });

Is equivalent to:

__::map(array(1, 2, 3), function($n) { return $n * 2; });

For the purposes of this article, I’m going to use the static method style.

Working with Collections and Arrays

Each

You can iterate through an array, applying a function to each item using __::each.

For example:

$items = array(1, 2, 3, 4, 5);
__::each($items, function($item) { print $item; });

Produces:

12345

Here’s a (slightly) more useful example:

$student_records = array(
    array(
        'name'    =>  'Joe Bloggs',
        'id'      =>  1,
        'grade'   =>  72,
        'class'   =>  'A',
    ),
    array(
        'name'    =>  'Jack Brown',
        'id'      =>  2,
        'grade'   =>  67,
        'class'   =>  'B',
    ),
    array(
        'name'    =>  'Jill Beaumont',
        'id'      =>  3,
        'grade'   =>  81,
        'class'   =>  'B',
    ),
);

__::each($student_records, function($record) { 
    print $record['name'] . ' ' . $record['grade'] . '<br />'; 
});

This produces:

Joe Bloggs A
Jack Brown B
Jill Beaumont B

Later, we’ll look at some better ways of doing something like this, when we delve into templating.

Pluck

If you have a multi-dimensional array and you wish to “pluck” certain values out and flatten them into a single-dimensional array, you can use __::pluck.

The Facebook API provides a real-world example of when this might be useful. When you request a list of a Facebook user’s friends, the result (when json_decode‘d into a multi-dimensional array) looks like this:

$response = array(
    'data'  =>  array(
        array(
            'name'  =>  'Joe Bloggs',
            'id'        =>   123456789,
        ),
        array(
            'name'  =>  'Jack Brown',
            'id'        =>  987654321,
        ),
    )
...

If we want to extract the Facebook user ID’s into a single-dimensional array, we can do this:

$ids = __::pluck($response['data'], 'id');
// array(123456789, 98765432)

Minimum and Maximum

Based on our sample student records from earlier, we could find the student with the highest grade using __::max:

__::max($student_records, function($student) { return $student['grade']; });
// returns array('name' => 'Jill Beaumont', 'id' => 3, 'grade' => 81, 'class' => 'B')

Or the lowest, by using __::min:

__::min($student_records, function($student) { return $student['grade']; });
// returns array('name' => 'Jack Brown', 'id' => 2, 'grade' => 67, 'class' => 'B')

As you can see, rather than simply return the highest or lowest grade, these functions return the corresponding item from the array – i.e., the student record.

Filter and Reject

The filter method runs a truth test on a collection or array, and returns only those items for which it passes.

As an example, let’s go back to our student records from earlier. Suppose the pass-mark is a grade of 70 or above. We can use __::filter to apply a simple function, so that we can find out which of the students have passed:

$passed = __::filter($student_records, function($student) { return $student['grade'] >= 70; });

The reject function is simply the opposite of filter; it excludes the items that pass the truth test.

In other words, the following two functions produce the same result:

__::filter($student_records, function($student) { return $student['grade'] >= 70; });

__::reject($student_records, function($student) { return $student['grade'] < 70; });

sortBy

The sortBy function orders an array – in ascending order – using an iterator function. Here’s a simple example:

$scores = array(476, 323, 1010, 567, 723, 1009, 600);
$sorted = __::sortBy($scores, function($score) { return $score; });

To sort in descending order, simply negate the value. So to get a list of records, starting with the student who scored highest:

$ordered = __::sortBy($student_records, function($student) { return -$student['grade']; });

groupBy

Suppose we want to re-organise our array of students according to the class they are in.

This is where groupBy comes in. We can do this:

var_dump( __::groupBy($student_records, 'class') );

Which will produce the following output:

array(2) {
["A"]=>
array(1) {
    [0]=>
    array(4) {
    ["name"]=>
    string(10) "Joe Bloggs"
    ["id"]=>
    int(1)
    ["grade"]=>
    int(72)
    ["class"]=>
    string(1) "A"
    }
}
["B"]=>
array(2) {
    [0]=>
    array(4) {
    ["name"]=>
    string(10) "Jack Brown"
    ["id"]=>
    int(2)
    ["grade"]=>
    int(67)
    ["class"]=>
    string(1) "B"
    }
    [1]=>
    array(4) {
    ["name"]=>
    string(13) "Jill Beaumont"
    ["id"]=>
    int(3)
    ["grade"]=>
    int(81)
    ["class"]=>
    string(1) "B"
    }
}
}

Reduce

The reduce function is used to reduce a collection into a single value.

For example, to get the sum of a single-dimensional array:

__::reduce(array(1, 2, 3), function($first, $second) { return $first + $second; }, 0); // 6

If we combine reduce with pluck on our student records, we can find out the average grade:

$average = round( ( __::reduce(__::pluck($student_records, 'grade'), function($first, $second) { return $first + $second; }, 0) / count($student_records) ), 2);

What we’re doing here is extracting the grades as a single dimensional array using pluck, reducing them to a single value using a simple additive iterator function, dividing it by the number of records, then rounding it to two digits.

Find

The find function iterates through an array, executing a function against each item until the function returns true – in other words, it returns the first matching “record”.

For example, to find the first student with a grade below 70, you could do this:

__::find($student_records, function($student) { return $student['grade'] < 70; })

This should produce the following, if you var_dump the results:

array(4) {
["name"]=>
string(10) "Jack Brown"
["id"]=>
int(2)
["grade"]=>
int(67)
["class"]=>
string(1) "B"
}

Suppose we want to find a student record by ID. We can do this:

function findById($records, $id) {
return __::find($records, function($record) use ($id) { return ($record['id'] == $id); });
}

If you run:

var_dump(findById($student_records, 2));

You should get this:

array(4) {
    ["name"]=>
    string(10) "Jack Brown"
    ["id"]=>
    int(2)
    ["grade"]=>
    int(67)
    ["class"]=>
    string(1) "B"
}

Note the inclusion of the use keyword, so that $id is in scope.

Templating

One area where Backbone uses Underscore extensively is through its simple templating functionality.

It’s generally much cleaner than, say, string concatenation, and you can combine it with other Underscore functions such as __::each to make it even more powerful.

Within a template string, you can echo values like this:

<%= $student['name'] %>

You can execute code using this syntax:

<% __::each($records, student) { %> … <% }) %>

There are two common methods for templating. One is to define it as a string, using the syntax above to inject values or code, and run it through the __::template() function:

$welcome = 'Hello <%= $name %>, welcome back!';
print __::template($welcome, array('name' => 'Jack'));

Alternatively, you can “compile” a template by defining a variable and assigning the result of the __::template function, with a template defined as a single, string argument.
The following is equivalent to the previous example:

$compiled = __::template('Hello <%= $name %>, welcome back!');
print $compiled(array('name'=>'Jack'));

// Hello Jack, welcome back!

Here’s how you might create a simple template to output an unordered list, by combining __::template with __::each:

$ul = __::template('<ul><% __::each($items, function($item)  { %><li><%= $item %></li><% }); %></ul>');

print $ul(array('items' => array('one', 'two', 'three')));

Let’s build a compiled template which takes a set of student records, and creates an unordered list of their names:

$list_students = __::template('<ul><% __::each($records,  function($student) { %><li><%= $student["name"] %></li><% }); %></ul>');

Then, to render it:

print $list_students(array('records' => $student_records));

You should get the following output:

<ul>
    <li>Joe Bloggs</li>
    <li>Jack Brown</li>
    <li>Jill Beaumont</li>
</ul>

Or a template to produce a table of students and their grades:

$grades_table = __::template('<table><thead><tr><td>Student</td><td>Grade</td></tr></thead><tbody><% __::each($records, function($student) { %><tr><td><%= $student["name"] %></td><td><%= $student["grade"] %>%</td></tr><% }); %></tbody></table>');

print $grades_table(array('records' => $student_records));

You can of course pass multiple arguments in, so we could add a header to the table, for example:

$grades_table = __::template('<h4><%= $title %></h4><table><thead><tr><td>Student</td><td>Grade</td></tr></thead><tbody><% __::each($records, function($student) { %><tr><td><%= $student["name"] %></td><td><%= $student["grade"] %>%</td></tr><% }); %></tbody></table>');

print $grades_table(array('title' => $title, 'records' => $student_records));

Extending Underscore

You can even create your own functions using mixins.

__::mixin(array(
'capitalize'=> function($string) { return ucwords($string); },
'shout'      => function($string) { return strtoupper($string); }
));
__::capitalize('joe bloggs'); // 'Joe Bloggs'
__::shout('Joe bloggs');       // 'JOE BLOGGS'

Summary

In this article, I’ve introduced you to the PHP port of the popular “utility belt” library, Underscore. I’ve gone through some of the available features; however there’s much more to explore. Have a browse through the documentation and play around!

Free JavaScript: Novice to Ninja Sample

Get a free 32-page chapter of JavaScript: Novice to Ninja

  • Wez Pyke

    Typo

    $items = array(1, 2, 3, 4, 5);
    _::each($items, function($item) { print $item; });

    Should be

    $items = array(1, 2, 3, 4, 5);
    __::each($items, function($item) { print $item; });

    • http://www.bitfalls.com/ Bruno Skvorc

      Thanks, fixed!

  • Vlakarados

    IMO this is the worst thing I’ve seen for PHP syntax wise. Semantically ruins the code because of the lazy developers that can’t seem to type two more characters and make their code clean and easy to read. While it may be a couple of useful functions they are implemented in a class that has a name indicating all of the class magic methods. IMO this should be renamed to something more readable and it will have it’s way into PHP where the current trend is to have really clean code with as much self explanatory names as possible. This is just a all-in-one helper which is simply ridiculous for the PHP language which is driven by the community to be more elegant and self-explanatory from all of it’s native issues like string format functions written in a multiple number of ways without any convention.

    • lukaswhite

      There’s actually another Underscore port – https://github.com/Anahkiasen/underscore-php – that uses syntax such as:

      Arrays::each

      or

      $array = new Arrays($array);
      $array->filter(…)->sort(…)->get(2)

      …but personally, speaking as someone who goes back and forth between PHP and Javascript a lot, I like the consistency of the approach I’ve outlined in the article. Which is probably what the developers intended.

      • http://www.letsnurture.co.uk/ ilesh Raval

        even i’m using it and i think it will work… as there is no issue with me even

  • bdwzr

    Typo?

    __::each($student_records, function($record) {
    print $record['name'] . ‘ ‘ . $person['grade'] . ”;
    });

    Should be

    __::each($student_records, function($record) {
    print $record['name'] . ‘ ‘ . $record['grade'] . ”;
    });

    • http://www.bitfalls.com/ Bruno Skvorc

      Fixed, thanks!

  • nyamsprod

    I have some reservations about this port.
    There are already many native PHP functions to deal with arrays.
    The library would have had an added value if it could work seemingly with PHP Iterators which it does not. And I agree with @sergeytelshevsky:disqus The syntax looks awful.
    Apart from that the templating part could have been great if PHP did not already have many entreprise grade templating engines (Twig, Smarty, Plates, etc…)

  • Keith Penderis

    Does PHP not do these sort of things natively? Does the PHP community need to have little libraries that remove them from the realities of the language as it is in javascript…

    • http://www.bitfalls.com/ Bruno Skvorc

      What is the point of this comment? This lib adds some useful functionality that isn’t there, that’s all.

  • Diego Vieira

    I don’t recommend using this. currently 28 issues on github, mostly dated from 1 year ago. no answer from the developer. some pull requests without merging. on my test, it didn;t worked with PHP 5.4 and some people complained that it’s not working with 5.5.