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.





