Practical Functional PHP

There are a lot of articles on internet about functional PHP. There is very nice presentation about it http://munich2012.drupal.org/program/sessions/functional-php that was held in Munich.

I was thinking about applying techniques to every-day coding and came up with two quite useful examples.

Cleaner code in form builders

Usually functions that generate form arrays are big. Sometimes they are very big and it is getting complicated to understand fast what elements are in the form and what are their properties.

This example is trying to address this problem.

<?php
  $textfield = array('#type' => 'textfield');
  $required = array('#required' => TRUE);
  $size = function($number) { return array('#size' => $number); };
  $size_maxlength = function($number) { return array('#size' => $number, '#maxlength' => $number); };
 
  $form['first_name']     = $textfield + $size(20);
  $form['last_name']      = $textfield + $size(20);
  $form['business_name']  = $textfield + $size(20);
 
  $form['account_number'] = $textfield + $required + $size(20);
  $form['pin'] =           $textfield + $required;
  $form['ssn'] =           $textfield + $required + $size_maxlength(4);
 
  $form['phone_number_part1'] = $textfield + $required + $size_maxlength(3);
  $form['phone_number_part2'] = $textfield + $required + $size_maxlength(3);
  $form['phone_number_part3'] = $textfield + $required + $size_maxlength(4);
?>

Now it is pretty clear what elements are and what properties they have. If you will ask about default values or titles – default values are assigned to elements using

<?php
foreach ($form as $form_key => &$element_form) {
    if (!isset($default_values[$form_key])) {
      continue;
    }
    $value_key = ($element_form['#type'] == 'value') ? '#value' : '#default_value';
    $element_form[$value_key] = $default_values[$form_key];
  }
?>

And all the titles are set up in theming function for this form.

In this example I like how compact are form constructors and how easy they are to read.

Recursive iterating in form arrays

Second example is also related to forms.

Once I have needed to set all form sub elements to be not required. And there was another function that disables all elements.

So functions looked like:

<?php
function custom_module_form_element_recursive_disable(&$element) {
  if (!is_array($element)) {
    return;
  }
  $element['#disabled'] = TRUE;
  foreach (element_children($element) as $key) {
    custom_module_form_element_recursive_disable($element[$key]);
  }
}
 
function custom_module_form_element_recursive_not_required(&$element) {
  if (!is_array($element)) {
    return;
  }
  if (!empty($element['#required'])) {
    $element['#required'] = FALSE;
  }
  foreach (element_children($element) as $key) {
    custom_module_form_element_recursive_disable($element[$key]);
  }
}
?>

As you can see it is pretty simple code but there is a lot of duplicate in it.

Using functional php we can avoid duplicating:

<?php
function _custom_module_recursive_form_visitor(&$element, $function) {
  if (!is_array($element)) {
    return;
  }
 
  $function($element);
 
  foreach (element_children($element) as $key) {
    _custom_module_recursive_form_visitor($element[$key], $function);
  }
}
 
function custom_module_form_element_recursive_disable(&$element) {
  $function = function(&$element) {
    $element['#disabled'] = TRUE;
  };
  _custom_module_recursive_form_visitor($element, $function);
}
 
function custom_module_form_element_recursive_not_required(&$element) {
  $function = function(&$element) {
    if (!empty($element['#required'])) {
      $element['#required'] = FALSE;
    }
  };
  _custom_module_recursive_form_visitor($element, $function);
}
?>

Now we can add other functions that need to perform actions recursively on form elements.

If you have some other usages of anonymous functions in PHP please share.

Thanks for reading.

By the way I have submitted a session for DrupalCon Portland http://portland2013.drupal.org/session/clean-code where I will touch this topic as well from perspective of keeping your code clean. So welcome to comment on it.

Comments

Thanks for posting this. It is always nice to get a different perspective into the tasks I perform nearly every day. I'll be implementing this on my next custom module.

Hi,

I quite like the syntax you've ended up with in your first example, but I can't see the real benefit of using anonymous functions to get there.

You could just have the size() and size_maxlength() (and why not do the same for textfield and required?) as normal functions in a library file somewhere, e.g.:

<?php
 
  // these would be defined in a library file somewhere
  // which would be included in at some point:
  function textfield() { return array('#type' => 'textfield'); }
  function required() { array('#required' => TRUE); }
  function size($number) { return array('#size' => $number); }
  function size_maxlength($number) { return array('#size' => $number, '#maxlength' => $number); }
 
 
  // then used like this:
  $form['first_name'] = textfield() + size(20);
  $form['ssn']        = textfield() + required() + size_maxlength(4);
?>
Like I say, I like the syntax you end with for defining the elements in the $form array, but what's the benefit to making that happen with anonymous functions?

Yes, I agree that with defining function you will get same thing. But with anonymous functions you don't have risk in namespace conflicts.

If you're interested, this error appeared after posting my comment: Notice: Undefined property: stdClass::$hostname in comment_notify_comment_insert() (line 270 of /var/www/ygerasimov.com/sites/all/modules/contrib/comment_notify/comment_notify.module).

Prior to starting out in Drupal about 2 years ago, I had a fair amount of jQuery experience where closures are pretty common. It's refreshing to see PHP users picking up on the versatility of anonymous functions.

Here's a snippet from a module that searches our LDAP directory. Anonymous functions give us a concise way of overriding the output of certain attributes:

<?php
function wsu_directory_config_query_fields() {
  $field['cn']['value'] = function ($entry) {
    return (isset($entry['personaltitle']) ? implode(' ', $entry['personaltitle']) . ' ' : '')
      . $entry['cn'][0] . (isset($entry['personalsuffix']) ? ', ' . implode(', ', $entry['personalsuffix']) : '')
      . (isset($entry['professionalsuffix']) ? ', ' . implode(', ', $entry['professionalsuffix']) : '');
  };
  $field['postaladdress']['label'] = 'Address';
  $field['telephonenumber']['label'] = 'Phone';
  $field['mail']['label'] = 'Email';
  $field['mail']['value'] = function ($entry) {
    return l(implode(', ', $entry['mail']), 'mailto:' . $entry['mail'][0]);
  };
  $field['url']['label'] = 'Website';
  $field['url']['value'] = function ($entry) {
    return l(implode(', ', $entry['url']), 'mailto:' . $entry['url'][0], array(
      'attributes' => array('rel' => 'nofollow', 'target' => '_blank'),
    ));
  };
  $field['wsurole']['label'] = 'Status';
 
  return $field;
}
 
/* In a function that outputs the fields: */
  if (isset($field['value']) && is_callable($field['value']))
      return $field['value']($entry);
    else
      /* If no callable defined, print as a plain, comma-separated list: */
      return implode(', ', $entry[$field_name]);
 
?>
(btw, the syntax highlighting doesn't seem to be working, at least on preview.)

Very nice example! Thanks!

Nice!! Really nice! Thanks Yuri for this great blog post - and comments.