Embedded components
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: rewardreward.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 aRulesComponent. If it does not, we provide a default that intakes aUserentity to evaluate. - In
updateFromComponent(), we get theRulesComponentand 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());
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion