Embedded components

Last updated on
11 March 2021

This documentation needs work. See "Help improve this page" in the sidebar.

In Drupal 7 we would generate Rules inside of the default hook, loosely coupled by a naming convention (e.g. mymodule_{key}). In Drupal 8 we will use RulesUiComponentProviderInterface to embed a component in a configuration entity. This guide assumes that you have already created a custom configuration entity. Let's assume the entity type and module is called "reward" and you want to add conditions to evaluate if the user should receive the reward or not.

Define route and *.rules_ui.yml

This will indicate that we want Rules UI functionality appended to a route that we will also create. It will also tell Rules that we want the component to be saved onto the object loaded from the reward parameter. Note how _rules_ui option on the route matches the plugin name and config_parameter matches the route parameter.

reward.rules_ui.yml:

reward.rules_ui_conditions:
  label: 'Embedded reward conditions'
  base_route: entity.reward.conditions
  settings:
    config_key: component
    config_parameter: reward

reward.routing.yml:

[...]
entity.reward.conditions:
  path: '/admin/reward/type/{reward}/conditions'
  defaults:
    _form: '\Drupal\reward\Form\RewardConditionsForm'
    _title: 'Reward conditions'
  requirements:
    _permission: 'administer reward'
  options:
    _rules_ui: reward.rules_ui_conditions
    parameters:
      reward:
        type: 'entity:reward'

Define new form for editing a component

This is a normal form that extends ConfigFormBase, but is provided with a Rules UI handler from the plugin definition that matches the route above. Most of this code is copied from rules_test_ui_embed:

RewardConditionsForm.php:

[...]
class RewardConditionsForm extends ConfigFormBase {

  /**
   * The RulesUI handler of the currently active UI.
   *
   * @var \Drupal\rules\Ui\RulesUiConfigHandler
   */
  protected $rulesUiHandler;

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'reward_conditions';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, RulesUiConfigHandler $rules_ui_handler = NULL) {
    $form = parent::buildForm($form, $form_state);
    $this->rulesUiHandler = $rules_ui_handler;

    $form['conditions'] = $this->rulesUiHandler->getForm()
      ->buildForm([], $form_state);

    $form['actions']['cancel'] = [
      '#type' => 'submit',
      '#limit_validation_errors' => [['locked']],
      '#value' => $this->t('Cancel'),
      '#submit' => ['::cancel'],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
    $this->rulesUiHandler->getForm()
      ->validateForm($form['conditions'], $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->rulesUiHandler->getForm()
      ->submitForm($form['conditions'], $form_state);

    // Save the configuration that submitForm() updated (the config entity).
    $config = $this->rulesUiHandler->getConfig();
    $config->save();

    // Also remove the temporarily stored component, it has been persisted now.
    $this->rulesUiHandler->clearTemporaryStorage();

    parent::submitForm($form, $form_state);
  }

  /**
   * Form submission handler for the 'cancel' action.
   */
  public function cancel(array $form, FormStateInterface $form_state) {
    $this->rulesUiHandler->clearTemporaryStorage();
    $this->messenger()->addMessage($this->t('Canceled.'));
    $form_state->setRedirectUrl($this->rulesUiHandler->getBaseRouteUrl());
  }

}

In buildForm we take in the Rules UI handler and use it to generate the condition form. In submitForm, the Rules UI handler will notify our Rules component “provider” that there is a component that has to be saved.

Implement RulesUiComponentProviderInterface

The rulesUiHandler from above requires the entity type to handle getting the Rules component and saving it onto itself since we are not specifying a static config_name or config_key We add the component property to config_export, then we implement RulesUiComponentProviderInterface and implement the 2 methods:

/**
 * @ConfigEntityType(
[...]
 *   config_export = {
 *     "id",
 *     "label",
 *     "component"
 *   },
 */
class Reward extends ConfigEntityBase implements RulesUiComponentProviderInterface {

  /**
   * {@inheritdoc}
   */
  public function getComponent() {
    if (empty($this->component)) {
      // Provide a default for now.
      $this->component = [
        'expression' => ['id' => 'rules_and'],
        'context_definitions' => [
          'user' => [
            'type' => 'entity:user',
            'label' => 'User',
            'description' => 'User to evaluate feedback',
          ],
        ],
      ];
    }

    if (!isset($this->componentObject)) {
      $this->componentObject = RulesComponent::createFromConfiguration($this->component);
    }
    return $this->componentObject;
  }

  /**
   * {@inheritdoc}
   */
  public function updateFromComponent(RulesComponent $component) {
    $this->component = $component->getConfiguration();
    $this->componentObject = $component;

    return $this;
  }

}
  • In getComponent() we check to see if the entity already has conditions, and return a RulesComponent. If it does not, we provide a default that intakes a User entity to evaluate.
  • In updateFromComponent(), we get the RulesComponent and store it on the entity.

Now if we create a new reward and visit /admin/reward/type/{reward}/conditions we can add arbitrary conditions to the reward. We can then invoke the rule in our code to validate the conditions:

// Load the reward entity, which has an embedded component

/* @var \Drupal\rules\Engine\RulesComponent $component */
$component = Reward::load('free_cheesesteak')->getComponent();

$account = \Drupal::currentUser();

// Set the user context
$component->setContextValue('user', $account);

// Evaluate the conditions
$gets_free_cheesesteak = $component->getExpression()->executeWithState($component->getState());

Tags

Help improve this page

Page status: Needs work

You can: