Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

The new condition plugin system allows for generic use of both conditional logic as well as conditional logic generating user interface components. This system is designed to centralize as many Boolean checks as possible so that things like user_permission, node_access, current path, etc can all be validated in a generic and reusable manner.

To implement a condition in a module create a class in {module}/src/Plugin/Condition/{ConditionName}.php and extend ConditionPluginBase (which implements ConditionInterface).

The class must also declare a plugin annotation in its docblock comment. See the example below and the @Condition annotation declaration in the class' docblock.

<?php

namespace Drupal\node\Plugin\Condition;

use Drupal\Core\Condition\ConditionPluginBase;

/**
 * Provides a 'Node Type' condition.
 *
 * @Condition(
 *   id = "node_type",
 *   label = @Translation("Node Bundle"),
 *   context = {
 *     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
 *   }
 * )
 *
 */
class NodeType extends ConditionPluginBase {
  /* Methods removed for brevity of the example.
}

Most condition plugin utilize plugin contexts. A high level overview of plugin contexts and using them can be found here: http://drupal.org/node/1938688

For purposes of demonstrating the basic approach, let's assume we have a few things at our disposal: a node entity and a user entity. With these available we can actually use any condition that consume either (or both) of them for context. As of the writing of this Change Notice we only have the node_type condition in core, but for purposes of this example, we're going to assume that we have a user_role, user_permission, node_type and language conditions (all of which except user_permission have patches in the issue queue).

If we already have our node and our user, and we wanted to do some sort of validation against these things, we could be checking that the node of of a particular type and that the user (maybe the global user) has a particular permission. That would look something like this:


$manager = \Drupal::service('plugin.manager.condition');
$node_check = $manager->createInstance('node_type', array('bundles' => array('article')));
$node_check->setContextValue('node', $node);
$user_check = $manager->createInstance('user_permission', array('permission' => 'my specific permission'));
$user_check->setContextValue('user', $user);

return $node_check->execute() && $user_check->execute();

This should check that the node is an article node and that the current user has 'my specific permission' permission. There are a number of ways to set configuration for condition plugins. The above method uses constructor based configuration injection. We can also utilize setter configuration. Let's do that in a new example. In our next example we want to check again that the node type is article, but this time we're going to load the user ourselves from the node author property and determine if the user has the administrator role.


$user = user_load($node->uid);
$manager = \Drupal::service('plugin.manager.condition');
$node_check = $manager->createInstance('node_type');
$node_check->setConfig('bundles', array('article'))
  ->setContextValue('node', $node);
$user_check = $manager->createInstance('user_role');
$user_check->setConfig('roles', array('administrator'))
  ->setContextValue('user', $user);

return $node_check->execute() && $user_check->execute();

We're now checking that the node is an article and that the author of that node is an administrator. If we wanted to check instead that the author is NOT an administrator, we could add one line to this and check for that instead:


$user = user_load($node->uid);
$manager = \Drupal::service('plugin.manager.condition');
$node_check = $manager->createInstance('node_type');
$node_check->setConfig('bundles', array('article'))
  ->setContextValue('node', $node);
$user_check = $manager->createInstance('user_role');
$user_check->setConfig('roles', array('administrator'))
  ->setConfig('negate', TRUE)
  ->setContextValue('user', $user);

return $node_check->execute() && $user_check->execute();

With the simple addition of a setConfig('negate', TRUE) on our specific condition, that condition will now return true when false and false when true, so our above condition now checks that the node type is article and that the author is not an administrator.

We've been injecting contexts into these before executing the conditions. This is the only method that should be absolutely requiring the context. Other condition methods are designed to work without contexts available. These are the buildForm() and summary() methods. Once a condition is configured, a summary() call will give you a human readable description of how the plugin is configured. So on our above $user_check, if we were to do the following:


print $user_check->summary();

That should yield something along the lines of: "The user is not a member of administrator".

Many conditions are capable of supporting multiple checks simultaneously, so they could check that a node is either a page or an article, or that a user is an administrator or an authenticated user. When writing a plugin of this sort, check existing plugins to see how to handle their summaries.

The condition plugin implement the FormInterface for their form needs. Plugins generally don't assume a use case if they're designed to be reusable. Conditions are a great example of such a plugin since block visibility is just one example of where we want to use them. User interfaces that need some sort of conditional configuration can make extensive use of these. Hypothetically we might want to configure multiple conditions and then execute them as a group. In this case our condition form callbacks are going to be necessary in order to exposing configuration to the end user who is using our user interface. I suggest a simple FormInterface wrapper for your use case that can simply instantiate the plugin and call $condition->buildForm()/validateForm()/submitForm() appropriately. Condition forms make no assumptions about where their values will be saved, they simply update the plugin's local configuration (which you can get by calling $condition->getConfig()) and then the implementation is in charge of saving the values as necessary. Likewise, condition plugins make no assumptions about redirects, so your wrapping FormInterface class can easily redirect as necessary. For more information of FormInterface usage, read: http://drupal.org/node/1932058

Current condition plugins in core:

  • Node type (provided by node module)
  • PHP (provided by php module)
  • Language (provided by language module)
Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done