Expressions

Last updated on
11 December 2021

The functionality of the Rules module may be extended via new Rules Expressions. Rules comes with expressions like "Rule set", "Action set", "Condition set (OR)" and "Condition set (AND)", and "Loop". Rules expressions are special constructs that aren't often used by contributed modules, but contributed modules may provide further plugins, e.g. to implement other kind of loops. The Rules Transformers module uses that to implement a pipeline plugin which just works like an action set but implements its own UI. Some other modules that provide their own Rules expressions are: Conditional Rules, Rules List Conditions, Rules Batch Loop, and Rules Factory UI. All these modules may be used to understand what role expressions play in Rules and why you might consider programming your own expression.

In Drupal 7, expressions are defined in hook_rules_plugin_info(). If you are already familiar with Rules expressions in Drupal 7, then Porting Expression Plugins will show you how D7 Rules expressions correspond to D8 Rules expressions. The remainder of this page does not assume prior knowledge of Rules in Drupal 7.

In Drupal 8, Expressions are no longer defined through a hook. They are plugins defined in PHP class extending ExpressionInterface or ExpressionContainerInterface, and are stored in <module_name>/src/Plugin/RulesExpression/.

The annotation is @RulesExpression.

To implement a Rules Expression plugin, place your plugin code in <module_name>/src/Plugin/RulesExpression/. For example:

/**
 * Evaluates a group of conditions with a logical OR.
 *
 * @RulesExpression(
 *   id = "rules_or",
 *   label = @Translation("Condition set (OR)"),
 *   form_class = "\Drupal\rules\Form\Expression\ConditionContainerForm"
 * )
 */
class OrExpression extends ConditionExpressionContainer {

  /**
   * {@inheritdoc}
   */
  public function evaluate(ExecutionStateInterface $state) {
    // Use the iterator to ensure the conditions are sorted.
    foreach ($this as $condition) {
      /* @var \Drupal\rules\Engine\ExpressionInterface $condition */
      if ($condition->executeWithState($state)) {
        $this->rulesDebugLogger->info('%label evaluated to %result.', [
          '%label' => $this->getLabel(),
          '%result' => 'TRUE',
        ]);
        return TRUE;
      }
    }
    $this->rulesDebugLogger->info('%label evaluated to %result.', [
      '%label' => $this->getLabel(),
      '%result' => 'FALSE',
    ]);
    // An empty OR should return TRUE. Otherwise, if all conditions evaluate
    // to FALSE we return FALSE.
    return empty($this->conditions);
  }

  /**
   * {@inheritdoc}
   */
  protected function allowsMetadataAssertions() {
    // We cannot guarantee child expressions are executed, thus we cannot allow
    // metadata assertions.
    return FALSE;
  }

}

The only required thing here is the plugin annotation to declare the id and label variables, and a public function evaluate() which accepts these context variables as arguments and returns a boolean indicating whether the condition was satisfied. This implementation can get more complicated if the evaluation of your condition requires services - then these should be injected into the plugin rather than making static \Drupal::service() calls. See Services and dependency injection in Drupal 8 for details.

Any helper functions needed for your condition, which in Drupal 7 you might have put into <module_name>.rules.inc, should be protected class methods declared in the Expression plugin class.

Rules Essentials provides an XOR condition set, as an example of how to write a Rules Expression and its accompanying tests. The XOR condition set performs a logical XOR on a set of conditions - this may not be very useful in practice, but it can sometimes address a tricky situation where you want one of two conditions to be true, but NOT both of the conditions. It also works for more than two conditions, but I can't think of a use case for that.

Help improve this page

Page status: No known problems

You can: