PHP: Some thoughts on using array_* with closures

The other day, I was hacking away on the PHP backend for the “Startup Institute” visualization and I realized it was going to need a good deal of array manipulation. Figuring it was as good a time as any, I decided to try and leverage PHP 5.3+ new closures along with the array_* functions to manipulate the arrays. I’m not well versed with functional programming but I’ve used Underscore.js’s array/collection functions so this is mostly in comparison to that.

The Array

The entire shebang is on GitHub but here is the gist of what we’re intersted in:

There is a CSV file that looks like ssdata.csv.sample except with more entries that is read into a list ($data) where every object has keys cooresponding to the values in the header. Thinking in JSON, the array ends up looking like:

[

{"fname": "Ashish", "lname": "Datta", "track": "dev", "email_address": "ashish@setfive.com"},

{"fname": "Matt", "lname": "Daum", "track": "dev", "email_address": "matt@setfive.com"}

...

]

Ok great, but now what can we do with it?

Sorting:

Using the usort function is particularly natural with closures. Compare the following:

<?php

/* Without closures: */

class ArraySorter {

  public static $sortKey = "";

  public function sort($a, $b){
    return strcmp($a[ ArraySorter::$sortKey ],  $b[ ArraySorter::$sortKey ]);
  }

}

ArraySorter::$sortKey = $sortKey;

usort($data, array("ArraySorter”, "sort”));


/* With closures: */

usort($data, function($a, $b) use($sortKey) {

  return strcmp($a[ $sortKey ],  $b[ $sortKey ]);

});

It’s pretty clear the version with closures is much shorter, more conscience, and ultimately easier to follow. Being able to “capture” the local $sortKey variable is also a key feature on the closure version since with the static version there’s no easy way to introduce variables into the sorting function.

Mapping:

In the linked example, I used array_map to basically convert an array of characters into an array of ASCII values for those characters.

<?php

/* Without: */

for($i = 0; $i < count($data["first_name"]); $i++){

 $data["first_name"][$i] = ord($data["first_name"][$i]);

}

/* With: */

$data["first_name"] = array_map(function($el){ return ord($el);}, $data["first_name"]);

With such a small map function, it’s hard to see or appreciate the benefits of using the closure along with array_map. With the closure though, you’ll get a couple of benefits including isolated scope so that you won’t inadvertently rely on the value of a variable that isn’t directly related to transforming the array values.

Using the closure would also “look” much cleaner if the array had non-numeric keys, since without being able to use integer indexes the for(…) loop would be more confusing.

Filter it:

This isn’t used but it could have been to return only the elements that were selected.

<?php

/* Without: */

$filteredArray = array();

foreach( $payload as $item ){

 if( (strlen($filterEmailAddress)

      && $item["email_address"] == $filterEmailAddress)

     || in_array($item["track"], $selectedTracks) ){

    $filteredArray[] = $item;

 }

}


/* With: */

$filteredArray = array_filter($payload, function($item) use ($filterEmailAddress, $selectedTracks){

 return ((strlen($filterEmailAddress)

            && $item["email_address"] == $filterEmailAddress)

            || in_array($item["track"], $selectedTracks));

});

Looking at the the version with the closure, its a bit easier to follow and since it’ll enforce scope isolation if the “truth test” was a bit more complicated you’d only have to debug what’s actually inside the closure. Also, not having to “skip” some elements leaves the code with a nicer feel and overall I’d argue its just better looking.

Overall Thoughts:

Overall, using closures with the array_* functions will definitely lead to cleaner, more concise, and easier to follow code. Unfortunately, there are a few rough spots. Like with most of the standard library, the argument order is inconsistent which is always a constant irritation. For example, for no apparent reason array_map is “callback, array” but array_filter is “array, callback”. Also, another irritation is that the “index” isn’t available inside several of the callbacks like on array_reduce or array_map.

Personally though, the biggest limitation is that none of the array_* functions will work with classes that implement the Traversable or Iterator interfaces. That means if you have a Doctrine_Collection and you want to reduce down to a single result you’re still stuck with a foreach(…).

Anyway, as always I’d love to hear other opinions in the comments.

Questions or comments about this? Email us at contact@setfive.com.

Read more