Speaker: Josef Dabernig a.k.a Dasjo
This webinar takes a look at how to automate some powerful backend capabilities for your Drupal site, using the Drupal 8 Rules. Take a look at how D8 Rules can change the way we build sites. Also, learn how you can contribute to getting Rules closer to a stable release.
11. Drupal 8 wins
⢠OOP, Dependency
Injection
⢠APIs
⢠PHPUnit
⢠Symfony2
⢠Removed legacy
modules
⢠Web services built in
⢠Front-end, responsive,
...
12. Drupal 8 for Rules
⢠Plug-in API
⢠Entity & Typed Data API
⢠Conditions API
⢠(Actions API -> forked)
⢠Context API shared w/ Core, Page Manager
⢠Configuration Management (CMI, YAML)
13. Reusable components (ârtoolsâ)
⢠Tokens (automatic, based on typed data)
⢠Typed data widgets & formatters
⢠Extended Context API
⢠Embeddable Rules UI components
⢠Actions & Conditions
⢠Rules data selector for tokens, contexts
17. #d8rules goals
⢠Accelerate Drupal 8 uptake by ensuring that
Rules as a key contributed module is ready,
early enough.
⢠Enable flexible workflows in Drupal 8 sites
that are easily configurable & reusable.
⢠Make Drupal contributions sustainable by
funding contributed code used on hundreds
of thousands of sites.
39. rules_user_login:
label: 'User has logged in'
category: 'User'
context:
account:
type: 'entity:user'
label: 'Logged in user'
Event example from rules.rules.events.yml
40. 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);
}
41. 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);
}
47. class EntityDelete extends RulesActionBase {
/**
* Executes the action with the given context.
*/
public function doExecute(EntityInterface $entity) {
$entity->delete();
}
}
48. class EntityDelete extends RulesActionBase {
/**
* Executes the action with the given context.
*/
public function doExecute(EntityInterface $entity) {
$entity->delete();
}
}
49. 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);
}
}
50. 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);
}
}
51. Context
â Defining context for a plug-in
â Using context within a plug-in
â Passing context to the plug-in
52. 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());
}
}
53. 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());
}
}
63. Describe data to Rules
Drupal 7
⢠hook_rules_data_info()
-> ?
⢠hook_entity_property_info_alter
-> ?
64. Typed Data API
⢠Consistent way of interacting
with any data based on metadata
⢠Part of Drupal 8 & Entity Fields
⢠Defines a type system for PHP:
â Primitive types (integer, float, string, dates, ..)
â Complex types
â Lists (with items of a specified type)
66. /**
* 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;
}
}
67. /**
* 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;
}
}
68. /**
* 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;
}
69. /**
* 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;
}
70. Describe data to rules
⢠hook_rules_data_info()
-> ?
⢠hook_entity_property_info_alter
-> ?
71. Describe data to rules
⢠hook_rules_data_info()
-> Data type plugins, Typed Data
⢠hook_entity_property_info_alter
-> hook_data_type_info_alter()
-> hook_entity_base_field_info/alter()
-> hook_entity_bundle_field_info/alter()
-> FieldItem::propertyDefintions()
72. Every content entity & field type
in Drupal 8 is supported
by Rules out-of-the box!
77. Lists & Multiple values
â list<foo> notation is gone
â âlistâ data type & per data type class
â Separate data definitions for lists vs. list
items
â Mark context as âmultipleâ
93. /**
* 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'];
}
96. Automated testing
⢠RulesUnitTestBase extends UnitTestCase
⢠Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, âŚ
⢠RulesIntegrationTestBase
⢠use ActionManager, ConditionManager,
TypedDataManager, âŚ
97. 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;
}
98. /**
* 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());
}
99. Automated testing
⢠RulesUnitTestBase extends UnitTestCase
⢠Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, âŚ
⢠RulesIntegrationTestBase
⢠use ActionManager, ConditionManager,
TypedDataManager, âŚ
100. Automated testing
⢠RulesUnitTestBase extends UnitTestCase
⢠Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, âŚ
⢠RulesIntegrationTestBase
⢠use ActionManager, ConditionManager,
TypedDataManager, âŚ
⢠RulesEntityIntegrationTestBase
102. /**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('DrupalCoreEntityEntityInterface');
$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());
103. /**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('DrupalCoreEntityEntityInterface');
$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());
104. /**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('DrupalCoreEntityEntityInterface');
$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());
105. /**
* Tests evaluating the condition.
*
* @covers ::evaluate()
*/
public function testConditionEvaluation() {
$entity = $this->getMock('DrupalCoreEntityEntityInterface');
$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());
109. trait StringTranslationTrait {
/**
* The string translation service.
*
* @var DrupalCoreStringTranslationTranslationInterface
*/
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, âŚ);
}
}
110. class MyClass {
use StringTranslationTrait;
public function __construct(
TranslationInterface $string_translation) {
$this->stringTranslation = $string_translation;
}
/**
* Does something.
*/
public function doSth() {
// ...
$string = $this->t('Something');
// ...
}
}