As developers, we're all constantly searching for the cleanest way to write something. Writing that elegant one-liner or refactoring something ugly gives us a deep sense of satisfaction. Unfortunately, working with Drupal forces us to deal with deeply nested arrays that encourage ugly, cumbersome code. It's just too easy to reach for a trusty foreach loop and plop some conditions inside each one. However, as our codebase matures and new rules are added and exceptions are made, these loops can tend towards chaos. Just by adding a new if/else, we can unwittingly double the possible code paths and make our code much more difficult to reason about later.
There's another way though... With some simple concepts from functional programming, we can avoid those ugly, deeply-nested loops and conditionals that we often see in the Drupal ecosystem. Those same patterns can also help us think about our program more rationally and break a problem down into smaller pieces. This leads to cleaner, more reason-able code.
array_reduce() has been around for a while now, but really came into its own with the introduction of anonymous functions in PHP 5.3. I never see it in the Drupal contrib space, but I think it’s an unfortunate byproduct of our past. Since many of us learned PHP through Drupal, we either don’t know it exists, or refrain from using it because it wouldn’t work in older core and contrib.
The array_reduce() function's signature is:
<br /> mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )<br />
where $callback has the following signature:
<br /> mixed callback ( mixed $carry , mixed $item )<br />
Imagine you want to get a sum of all positive values in a nested array. That array might look like this:
<br /> $my_array = array(<br /> 0 => array(<br /> 'name' => 'foo',<br /> 'value' => 5,<br /> ),<br /> 1 => array(<br /> 'name' => 'bar',<br /> 'value' => -10,<br /> ),<br /> 2 => array(<br /> 'name' => 'baz',<br /> 'value' => 15,<br /> ),<br /> 3 => array(<br /> 'name' => 'zap',<br /> 'value' => 20,<br /> ),<br /> );<br />
With a foreach you could write a loop that looked something like this:
<br /> $sum = 0;</p> <p>foreach ($my_array as $item) {<br /> $sum = ($item['value'] > 0) ? $sum + $item['value'] : $sum;<br /> }</p> <p>// $sum == 40<br />
With array_reduce() you would write the same thing like this:
<br /> $sum_values = function ($carry, $item) {<br /> return ($item['value'] > 0) ? $carry + $item['value'] : $carry;<br /> };</p> <p>$sum = array_reduce($my_array, $sum_values, 0);</p> <p>// $sum == 40<br />
For every item in the array, the anonymous function gets passed the result of the previous iteration. On the first item, it will be passed NULL or an initial value if one was passed to array_reduce (in our example, we passed 0 as the initial value). After the reduction function has been called for every item, the last thing returned from the anonymous function is returned from array_reduce() itself.
In this trivial example, its advantages are hard to see. A foreach is nearly as short and just as easy to reason about. array_reduce's real strength lies in the idea of thinking of each operation on an item as a stateless function instead of a stateful imperitive loop.
This functional concept lets your mind reason more clearly about the problem at hand. When writing any logic, one must think "What do I know at this moment and what information must I return?" This constraint often helps me see the problem in a new, simpler way.
Imagine that you wanted to exclude any items named 'foo' from your sum. Having written a foreach, we'd be tempted to start nesting conditionals.
<br /> $sum = 0;</p> <p>foreach ($my_array as $item) {<br /> if ($item['name'] != 'foo') {<br /> $sum = ($item['value'] > 0) ? $sum + $item['value'] : $sum;<br /> }<br /> }</p> <p>// $sum == 35<br />
Doing things this way forces us to layer on logic, creating exponentially more possible code paths every time we add a rule. Instead, by using anonymous functions, array_reduce, and its sister function array_filter we can do this much more elegantly. array_filter’s signature is very similar:
<br /> array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )<br />
It’s callback is even simpler than array_reduce’s. It simply receives one item from the array as its only parameter. It should return TRUE to keep the item or FALSE to filter it out.
Putting everything together, we can define a filtering function and a reduction function independently of one another and then compose them to achieve the same result that we did with the nested loop and conditional. That might look something like this:
<br /> $remove_foos = function ($name) {<br /> return ($name != 'foo');<br /> };</p> <p>$sum_values = function ($carry, $item) {<br /> return ($item['value'] > 0) ? $carry + $item['value'] : $carry;<br /> };</p> <p>$sum = array_reduce(array_filter($my_array, $remove_foos), $sum_values, 0);</p> <p>// $sum == 35<br />
Using this strategy, each operation is made apparent and separate. First, 'foo's are filtered out, then positives are summed. That's a heck of a lot simpler to grasp later. As you add more rules, think about creating simple, focused functions that can be put together, or composed. By doing so, you’ll be able to reuse a lot more of these filters and reducers and recognize patterns more easily.
array_map is the third leg of this trifecta, it just doesn’t come up nearly as often in my day-to-day Drupal work (I think it’s probably the most powerful of the three, but I’ll leave you to look that one up). By using these three functions, we can implement some simple functional concepts in our PHP, untangling nested logic into discrete single-purpose functions. Clean, simple, and reason-able.