A regular expression Views filter handler

I've needed to build a regular expression filter for a view I'm working on, so I'm sharing the code here because it might be helpful to other people as well. My specific case is that I am building a Blocks administration VBO. I'd like to let the administrator filter on block body content, and allow them to enter a regular expression as a filter.

I first declare the relevant field in the Views schema for the block table:

// @file views_block.views.inc
function views_block_views_data() {
  [..]
  // Body
  $data['block_custom']['body'] = array(
    'title' => t('Body'),
    'help' => t('The block body.'),
    'field' => array(
      'handler' => 'views_handler_field',
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_regex',
    ),
  );
  [..]
  return $data;
}

Now adding the regex handler is a matter of implementing the views_handler_filter_regex class. I want my handler to support MySQL, PostgreSQL, and any other database system that supports regular expressions. Here's some minimal code to achieve this:

// @file views_handler_filter_regex.inc
class views_handler_filter_regex extends views_handler_filter {
  var $always_multiple = TRUE;

  function operator_options() {
    // Return placeholders that will be expanded at query creation time.
    return array(
      'match' => t('Matches regex'),
      'nomatch' => t('Does not match regex'),
    );
  }

  function admin_summary() {
    if (!empty($this->options['exposed'])) {
      return t('exposed');
    }
    return parent::admin_summary();
  }

  function value_form(&$form, &$form_state) {
    $form['value'] = array(
      '#type' => 'textfield',
      '#title' => t('Value'),
      '#size' => 30,
      '#default_value' => $this->value,
    );
  }

  function query() {
    // Find actual regex operators depending on database type.
    $db_type = Database::getConnection()->databaseType();
    switch ($db_type) {
      case 'mysql':
        $match = 'REGEXP';
        $nomatch = 'NOT REGEXP';
        break;
      case 'pgsql':
        $match = '*~'; // case insensitive match
        $nomatch = '!*~'; 
        break;
      default:
        // Allow other modules to define these operators.
        $operators = &drupal;_static(__METHOD__);
        if (empty($operators)) {
          // hook_views_regex_operators($db_type)
          // @param $db_type - the type of database engine being used ('mysql' and 'pgsql' will not be called).
          // @return array('match' => operator for matching, 'not match' => operator for negative matching);
          $operators = module_invoke_all('views_regex_operators', $db_type);
        }
        if (empty($operators)) {
          watchdog('views_regex', 'No regex operators found for database type %type. Using operator LIKE instead.', array('%type' => $db_type));
          $match = 'LIKE';
          $nomatch = 'NOT LIKE';
        }
        else {
          $match = $operators['match'];
          $nomatch = $operators['not match'];
        }
    }
    // Replace placeholder with actual operator.
    $this->operator = $this->operator === 'match' ? $match : $nomatch;
    parent::query();
  }
}

That's it! Short and sweet.

Comments

I thought views had built in support for regular expressions? Isn't there an operator for string values to support regex?

As a lead maintainer for Views, you should know ;-)

But since you ask, core Views supports regex for MySQL only, in the views_handler_filter_string and views_handler_filter_numeric handlers. My snippet here supports PgSQL, and allows for extensibility.