Coding with Rules�for Drupal 8
Josef Dabernig, Amazee Labs
twitter.com/dasjo
#d8rules team
Rules
Rules
Rules
Rules
Rules
Drupal 8 wins
Drupal 8 wins
Drupal 8 for Rules
Reusable components (“rtools”)
Reusable components (“rtools”)
Planned for site building
Campaign
#d8rules goals
Sponsors
Funding state
Rules 8.x �development status
Rules 8.x M1�Rules core API fundamentals
Rules 8.x M2�Rules core completion with basic UI
Rules 8.x M3�Rules release
Sprints, trainings & sessions
Szeged, Portoroz, Austin, Drupalaton, Amsterdam, Zurich, Bogotá, Vienna, Milano, Montpellier, Bratislava, Sunderland, Barcelona, Mumbai, Heidelberg, Granada, ..
Contribution
Getting started
Diving into Rules 8.x
Writing �Conditions, Actions & Events
Plug-ins
Provide conditions
Provide conditions
/**
* Provides a 'Node is sticky' condition.
*
* @Condition(
* id = "rules_node_is_sticky",
* label = @Translation("Node is sticky"),
* category = @Translation("Node"),
* context = {…}
* )
*/
class NodeIsSticky extends RulesConditionBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
$node = $this->getContextValue('node');
return $node->isSticky();
}
}
Provide actions
/**
* Provides a 'Delete entity' action.
*
* @Action(
* id = "rules_entity_delete",
* label = @Translation("Delete entity"),
* category = @Translation("Entity"),
* context = {…}
* )
*/
class EntityDelete extends RulesActionBase {
/**
* {@inheritdoc}
*/
public function execute() {
$entity = $this->getContextValue('entity');
$entity->delete();
}
}
Provide events
Event example from rules.rules.events.yml
rules_user_login:
label: 'User has logged in'
category: 'User'
context:
account:
type: 'entity:user'
label: 'Logged in user'
Event invocation
/**� * Implements hook_user_login().� */�function rules_user_login($account) {� // Set the account twice on the event: as the main subject but also in the� // list of arguments.� $event = new UserLoginEvent($account, ['account' => $account]);� $event_dispatcher = Drupal::service('event_dispatcher');� $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);�}
Event invocation
/**� * Implements hook_user_login().� */�function rules_user_login($account) {� // Set the account twice on the event: as the main subject but also in the� // list of arguments.� $event = new UserLoginEvent($account, ['account' => $account]);� $event_dispatcher = Drupal::service('event_dispatcher');� $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);�}
Context
Context
/**
* Provides a 'Delete entity' action.
*
* @Action(
* id = "rules_entity_delete",
* label = @Translation("Delete entity"),
* category = @Translation("Entity"),
* context = {…}
* )
*/
class EntityDelete extends RulesActionBase {
/**
* {@inheritdoc}
*/
public function execute() {
$entity = $this->getContextValue('entity');
$entity->delete();
}
}
/**
* Provides a 'Delete entity' action.
*
* @Action(
* id = "rules_entity_delete",
* label = @Translation("Delete entity"),
* category = @Translation("Entity"),
* context = {
* "entity" = @ContextDefinition("entity",
* label = @Translation("Entity"),
* description = @Translation("Specifies the entity,� which should be deleted permanently.")
* )
* }
* )
*/
class EntityDelete extends RulesActionBase {...}
Context
class EntityDelete extends RulesActionBase {
/**
* Executes the action with the given context.
*/
public function doExecute(EntityInterface $entity) {
$entity->delete();
}
}
class EntityDelete extends RulesActionBase {
/**
* Executes the action with the given context.
*/
public function doExecute(EntityInterface $entity) {
$entity->delete();
}
}
class FetchEntityById extends RulesActionBase implements ContainerFactoryPluginInterface {
/**
* Executes the action with the given context.
*/
public function doExecute($entity_type, $entity_id) {
$storage = $this->entityManager->getStorage($entity_type);
$entity = $storage->load($entity_id);
$this->setProvidedValue('entity', $entity);
}
}
class FetchEntityById extends RulesActionBase implements ContainerFactoryPluginInterface {
/**
* Executes the action with the given context.
*/
public function doExecute($entity_type, $entity_id) {
$storage = $this->entityManager->getStorage($entity_type);
$entity = $storage->load($entity_id);
$this->setProvidedValue('entity', $entity);
}
}
Context
class ListCountIsTest extends RulesIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
// Test that the list count is greater than 2.
$this->condition
->setContextValue('list', [1, 2, 3, 4])
->setContextValue('operator', '>')
->setContextValue('value', '2');
$this->assertTrue($condition->evaluate());
}
}
class ListCountIsTest extends RulesIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
// Test that the list count is greater than 2.
$this->condition
->setContextValue('list', [1, 2, 3, 4])
->setContextValue('operator', '>')
->setContextValue('value', '2');
$this->assertTrue($condition->evaluate());
}
}
Storing configuration
Storing configuration
Storing configuration
Drupal 8: CMI
Provide default rule configs
Provide default rule configs
langcode: en
status: true
dependencies: {}
id: rules_test_default_component
label: Rules test default component
module: rules
description: 'Tests adding Rules component by default.'
tag: 'test'
core: 8.x
expression_id: rules_rule
configuration:��...
configuration:
id: rules_rule
context:
user:
type: 'entity:user'
label: User
conditions:
id: rules_and
conditions: {}
actions:
id: rules_action_set
actions:
- id: rules_action
action_id: rules_system_message
context_mapping:
message: 'user:mail:value'
Executing the component
$config_entity = RulesComponent::load('rules_test_default_component');
$expression = $config_entity->getExpression();
$expression
->setContextValue('user', \Drupal::currentUser())
->execute();
Describe data to Rules
Drupal 7
Typed Data API
Data types
/**
* The float data type.
*
* The plain value of a float is a regular PHP float. For setting the value
* any PHP variable that casts to a float may be passed.
*
* @DataType(
* id = "float",
* label = @Translation("Float")
* )
*/
class FloatData extends PrimitiveBase implements FloatInterface {
/**
* {@inheritdoc}
*/
public function getCastedValue() {
return (float) $this->value;
}
}
/**
* The float data type.
*
* The plain value of a float is a regular PHP float. For setting the value
* any PHP variable that casts to a float may be passed.
*
* @DataType(
* id = "float",
* label = @Translation("Float")
* )
*/
class FloatData extends PrimitiveBase implements FloatInterface {
/**
* {@inheritdoc}
*/
public function getCastedValue() {
return (float) $this->value;
}
}
/**
* Plugin implementation of the 'link' field type.
*
* @FieldType(
* id = "link",
* label = @Translation("Link"),
* description = @Translation(“..."),
* default_widget = "link_default",
* default_formatter = "link",
* constraints = {"LinkType" = {}}
* )
*/
class LinkItem extends FieldItemBase implements LinkItemInterface {
public static function propertyDefinitions(� FieldStorageDefinitionInterface $field_definition) {
$properties['url'] = DataDefinition::create('string')
->setLabel(t('URL'));
…
return $properties;
}
/**
* Plugin implementation of the 'link' field type.
*
* @FieldType(
* id = "link",
* label = @Translation("Link"),
* description = @Translation(“..."),
* default_widget = "link_default",
* default_formatter = "link",
* constraints = {"LinkType" = {}}
* )
*/
class LinkItem extends FieldItemBase implements LinkItemInterface {
public static function propertyDefinitions(� FieldStorageDefinitionInterface $field_definition) {
$properties['url'] = DataDefinition::create('string')
->setLabel(t('URL'));
…
return $properties;
}
Describe data to rules
Describe data to rules
�
Every content entity & field type �in Drupal 8 is supported �by Rules out-of-the box!
[META] Rules 8.x UI
[META] Rules 8.x UI
d8rules.org - dasjo
Lists & Multiple values
Creating a Rule with context
$rule = $this->expressionManager->createRule([
'context_definitions' => [
'test' => [
'type' => 'string',
'label' => 'Test string',
],
],
]);
$rule = $this->expressionManager->createRule([
'context_definitions' => [
'test' => [
'type' => 'string',
'label' => 'Test string',
],
],
]);
$rule->addCondition('rules_test_string_condition',� ContextConfig::create()
->map('text', 'test')�);
$rule = $this->expressionManager->createRule([
'context_definitions' => [
'test' => [
'type' => 'string',
'label' => 'Test string',
],
],
]);
$rule->addCondition('rules_test_string_condition',� ContextConfig::create()
->map('text', 'test')�);
$rule->addAction('rules_test_log');
$rule = $this->expressionManager->createRule([
'context_definitions' => [
'test' => [
'type' => 'string',
'label' => 'Test string',
],
],
]);
$rule->addCondition('rules_test_string_condition',� ContextConfig::create()
->map('text', 'test')�);
$rule->addAction('rules_test_log');
$rule->setContextValue('test', 'test value');
$rule = $this->expressionManager->createRule([
'context_definitions' => [
'test' => [
'type' => 'string',
'label' => 'Test string',
],
],
]);
$rule->addCondition('rules_test_string_condition',� ContextConfig::create()
->map('text', 'test')�);
$rule->addAction('rules_test_log');
$rule->setContextValue('test', 'test value');
$rule->execute();
Mapping required and provided context
$rule = $this->expressionManager->createRule();
$rule = $this->expressionManager->createRule();
// Condition provides a "provided_text" variable.
$rule->addCondition('rules_test_provider');
$rule = $this->expressionManager->createRule();
// Condition provides a "provided_text" variable.
$rule->addCondition('rules_test_provider');
// Action provides a "concatenated" variable.
$rule->addAction('rules_test_string', � ContextConfig::create()
->provideAs('text', 'provided_text')
);
$rule = $this->expressionManager->createRule();
// Condition provides a "provided_text" variable.
$rule->addCondition('rules_test_provider');
// Action provides a "concatenated" variable.
$rule->addAction('rules_test_string', � ContextConfig::create()
->provideAs('text', 'provided_text')
);
// Same action again now provides "concatenated2"
$rule->addAction('rules_test_string', � ContextConfig::create()
->map(text, 'concatenated')
->provideAs('concatenated', 'concatenated2')
);
$rule = $this->expressionManager->createRule();
// Condition provides a "provided_text" variable.
$rule->addCondition('rules_test_provider');
// Action provides a "concatenated" variable.
$rule->addAction('rules_test_string', � ContextConfig::create()
->provideAs('text', 'provided_text')
);
// Same action again now provides "concatenated2"
$rule->addAction('rules_test_string', � ContextConfig::create()
->map(text, 'concatenated')
->provideAs('concatenated', 'concatenated2')
);
$rule->execute();
Advanced / under the hood
Provide other Rules plugins
Provide input evaluators
/**
* A data processor for applying numerical offsets.
*
* The plugin configuration must contain the following entry:
* - offset: the value that should be added.
*
* @RulesDataProcessor(
* id = "rules_numeric_offset",
* label = @Translation("Apply numeric offset")
* )
*/
class NumericOffset extends PluginBase implements RulesDataProcessorInterface {
/**
* {@inheritdoc}
*/
public function process($value) {
return $value + $this->configuration['offset'];
}
}
Automated testing
Automated testing
Automated testing
class DataListCountIs extends RulesConditionBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
$list = $this->getContextValue('list');
$operator = $this->getContextValue('operator');
$value = $this->getContextValue('value');
switch ($operator) {
case '==':
return count($list) == $value;
case '<';
return count($list) < $value;
case '>';
return count($list) > $value;
}
}
}
class ListCountIsTest extends RulesIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
// Test that the list count is greater than 2.
$condition = $this->condition
->setContextValue('list', [1, 2, 3, 4])
->setContextValue('operator', '>')
->setContextValue('value', '2');
$this->assertTrue($condition->evaluate());
// Test that the list count is not equal to 0.
$condition = $this->condition
->setContextValue('list', [1, 2, 3])
->setContextValue('operator', '==')
->setContextValue('value', '0');
$this->assertFalse($condition->evaluate());
}
}
Automated testing
Automated testing
/**
* @Condition(
* id = "rules_entity_is_of_type",
* label = @Translation("Entity is of type"),
* category = @Translation("Entity"),
* context = {
* "entity" = @ContextDefinition("entity",
* label = @Translation("Entity"),
* ),
* "type" = @ContextDefinition("string",
* label = @Translation("Type"),
* )
* }
* )
*/
class EntityIsOfType extends RulesConditionBase {
public function evaluate() {
$provided_entity = $this->getContextValue('entity');
$specified_type = $this->getContextValue('type');
$entity_type = $provided_entity->getEntityTypeId();
// Check whether the entity type matches.
return $entity_type == $specified_type;
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
$entity->expects($this->exactly(2))
->method('getEntityTypeId')
->will($this->returnValue('node'));
// Add the test node to our context as the evaluated entity.
// First, test with a value that should evaluate TRUE.
$this->condition->setContextValue('entity', $entity)
->setContextValue('type', 'node');
$this->assertTrue($this->condition->evaluate());
// Then test with values that should evaluate FALSE.
$this->condition->setContextValue('type', 'taxonomy_term');
$this->assertFalse($this->condition->evaluate());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
$entity->expects($this->exactly(2))
->method('getEntityTypeId')
->will($this->returnValue('node'));
// Add the test node to our context as the evaluated entity.
// First, test with a value that should evaluate TRUE.
$this->condition->setContextValue('entity', $entity)
->setContextValue('type', 'node');
$this->assertTrue($this->condition->evaluate());
// Then test with values that should evaluate FALSE.
$this->condition->setContextValue('type', 'taxonomy_term');
$this->assertFalse($this->condition->evaluate());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
$entity->expects($this->exactly(2))
->method('getEntityTypeId')
->will($this->returnValue('node'));
// Add the test node to our context as the evaluated entity.
// First, test with a value that should evaluate TRUE.
$this->condition->setContextValue('entity', $entity)
->setContextValue('type', 'node');
$this->assertTrue($this->condition->evaluate());
// Then test with values that should evaluate FALSE.
$this->condition->setContextValue('type', 'taxonomy_term');
$this->assertFalse($this->condition->evaluate());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
$entity->expects($this->exactly(2))
->method('getEntityTypeId')
->will($this->returnValue('node'));
// Add the test node to our context as the evaluated entity.
// First, test with a value that should evaluate TRUE.
$this->condition->setContextValue('entity', $entity)
->setContextValue('type', 'node');
$this->assertTrue($this->condition->evaluate());
// Then test with values that should evaluate FALSE.
$this->condition->setContextValue('type', 'taxonomy_term');
$this->assertFalse($this->condition->evaluate());
}
}
RulesState
RulesState
Traits
trait StringTranslationTrait {
/**
* The string translation service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $stringTranslation;
/**
* Translates a string to the current language or to a given language.
*
* See the t() documentation for details.
*/
protected function t($string, …) {
return $this->getStringTranslation()->translate($string, …);
}
}
class MyClass {
use StringTranslationTrait;
public function __construct(� TranslationInterface $string_translation) {
$this->stringTranslation = $string_translation;
}
/**
* Does something.
*/
public function doSth() {
// ...
$string = $this->t('Something');
// ...
}
}
Traits
Outro
Sprint with us!
Sprint with us!
Fluxkraft UI proposals
Rules Transformers
NoFlo