© Ibuildings 2014/2015 - All rights reserved
#DrupalDaysEU
Web-automation in Drupal 8
with
#DrupalDaysEU
© Ibuildings 2014/2015 - All rights reserved
Gold Sponsors
#DrupalDaysEU
© Ibuildings 2014/2015 - All rights reserved
Media Sponsors
Silver Sponsors
Wolfgang Ziegler // fago
Drupal Core: Entity & Forms
Contrib: Entity API, Rules,
Profile2, Field Collection, ...
based in Vienna
CEO - Head of Development
@the_real_fago
@dasjo
#d8rules team
• fago (Rules creator)
• klausi (co-maintainer)
• fubhy (developer)
• nico grienauer (design)
• dasjo (communication)
Rules
Rules
Rules
Rules
• Build flexible workflows
using events, condition &
actions
• Send customized mails to
notify your users about
updates
• Create custom
redirections,
system messages,
breadcrumbs
Rules
• Build flexible workflows
using events, condition &
actions
• Send customized mails to
notify your users about
updates
• Create custom
redirections,
system messages,
breadcrumbs
• 250.000+ reported
installations
(every 5th Drupal site)
• Hundreds of integration
modules
• Entity API, Fields, Views,
Webform, Context,
Features, Search API,
Tokens, Paths, Menus,
Queue or Field formatter
Drupal 8 wins
Drupal 8 wins
• OOP
• APIs
• PHPUnit
• Symfony2
• Removed legacy
modules
• Web services built in
• Front-end,
responsive, ...
For developers
• Drupal 8 DX
• Plug-in system
• Complete Entity API
• Deployable config via CMI
Reusable components
• Context API shared w/ Core, Page Manager
• Tokens (automatic based on typed data)
• Typed data widgets & formatters
• Embeddable Rules UI components
• Actions & Conditions
• Rules data selector for tokens, contexts
Reusable components
For site builders
• Admin UI usability improvements
• Simple Views Bulk operations in core
• “Inline Rules” instead of Rule sets / Rules
conditional
Campaign
#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.
drupalfund.us
Sponsors
Funding state
Rules 8.x
development status
Rules 8.x M1
Rules core API fundamentals
➡ Rules core engine, plugin types
➡ Align Rules condition & action APIs with core
➡ Parameter configuration & Context mapping
Rules 8.x M2
Rules core completion
• Complete Rules engine features
• Rules plugins, part two (Events, Loops, ...)
• Entity token support
• Configuration entity, CMI and integrity (started!)
• Generic Rules integrations
Rules 8.x M3
Rules release
• Rules UI
• Reusable UI components
• Rules scheduler port
• Port existing integration (started!)
Sprints & trainings
• DrupalCamp Alpe-Adria May 2014
• DrupalCon Austin June 2014
• Drupalaton August 2014
• DrupalCon Amsterdam September 2014
• Global Sprint Days January 2015
Contribution
• 50 forks, 22 contributors
• paranoik, stevepurkiss, scuts, jibran, chindris, omissis,
ndewhurst, jzavrl, MegaChriz, bbujisic, dawehner,
torenware, bartfeenstra, M1r1k, rinasek, joashuataylor,
lokapujya, icanblink, ...
• 132 closed pull requests, Thank you all!
Getting started
• Setup Drupal 8
• Fork Rules 8.x on github:
• https://github.com/fago/rules
Diving into Rules 8.x
Basics
Describe data to Rules
• hook_rules_data_info()
-> ?
• hook_entity_property_info_alter
-> ?
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)
Data types
• any
• string, integer, uri,
float, ...
• email
• timestamp,
datetime_iso8601
• timespan,
duration_iso8601
• entity
• entity:node
• entity:comment
• field_item
• field_item:string
• field_item:text
• field_item:image
/**
* 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 Float 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 Float 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'));
$properties['title'] = DataDefinition::create('string')
->setLabel(t('Link text’));
…
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'));
$properties['title'] = DataDefinition::create('string')
->setLabel(t('Link text’));
…
return $properties;
Describe data to rules
• hook_rules_data_info()
-> ?
• hook_entity_property_info_alter
-> ?
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()
Every
content entity
&
field type
in Drupal 8 is supported
by Rules
out-of-the box!
Writing
Conditions, Actions &
Events
Plug-ins
• OOP
• Annotations
• Auto-loading
• Discovery
• Derivatives
Provide conditions
• hook_rules_condition_info()
-> ?
Provide conditions
• hook_rules_condition_info
-> Implement a Condition plug-in
/**
* 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
• hook_rules_action_info
-> Implement an Action plug-in
/**
* 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
• hook_rules_event_info
-> Implement an Event plug-in
Provide events
• hook_rules_event_info
-> Implement an Event plug-in
(not implemented, yet)
Context
Context
• Specifying the context of a plugin
/**
* 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 {
/**
* {@inheritdoc}
*/
public function execute() {
$entity = $this->getContextValue('entity');
$entity->delete();
Context
• Specifying the context of a plugin
Context
• Specifying context
• Using context
class DataListItemRemove extends RulesActionBase {
/**
* {@inheritdoc}
*/
public function execute() {
$list = $this->getContextValue('list');
$item = $this->getContextValue('item');
foreach (array_keys($list, $item) as $key) {
unset($list[$key]);
}
$this->setContextValue('list', $list);
}
}
class DataListItemRemove extends RulesActionBase {
/**
* {@inheritdoc}
*/
public function execute() {
$list = $this->getContextValue('list');
$item = $this->getContextValue('item');
foreach (array_keys($list, $item) as $key) {
unset($list[$key]);
}
$this->setContextValue('list', $list);
}
}
class DataListItemRemove extends RulesActionBase {
/**
* {@inheritdoc}
*/
public function execute() {
$list = $this->getContextValue('list');
$item = $this->getContextValue('item');
foreach (array_keys($list, $item) as $key) {
unset($list[$key]);
}
$this->setContextValue('list', $list);
}
}
class FetchEntityById extends RulesActionBase implements
ContainerFactoryPluginInterface {
…
/**
* {@inheritdoc}
*/
public function execute() {
$entity_type = $this->getContextValue('entity_type');
$entity_id = $this->getContextValue('entity_id');
$storage = $this->entityManager->getStorage($entity_type);
$entity = $storage->load($entity_id);
$this->setProvidedValue('entity', $entity);
}
}
class FetchEntityById extends RulesActionBase implements
ContainerFactoryPluginInterface {
…
/**
* {@inheritdoc}
*/
public function execute() {
$entity_type = $this->getContextValue('entity_type');
$entity_id = $this->getContextValue('entity_id');
$storage = $this->entityManager->getStorage($entity_type);
$entity = $storage->load($entity_id);
$this->setProvidedValue('entity', $entity);
}
}
Context
• Specifying context
• Using context
Context
• Specifying context
• Using context
• getting context
• setting context
• setting provided values
Storing configuration
Storing configuration
• D7: Entity exportables
• Rules:
• RulesPlugin->export()
• entity_var_json_export()
• rules_import()
• RulesEntityController->import()
Storing configuration
• D7: Entity exportables
• Rules:
• RulesPlugin->export()
-> RulesExpression::getConfiguration()
RulesEntityController->import()
-> RulesExpression::setConfiguration()
Drupal 8: CMI
• Rules leverages the Config System
• Two types of config entities:
• Reaction Rules (todo)
• Components (WIP)
• Inherit features from CMI
• Config deployment / import&export / sync
• Config Translation (instead of Entity i18n)
• Default config
Provide default rule configs
• hook_default_rules_configuration()
-> *.rules.yml via CMI
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();
No UI, yet!
Fluxkraft UI proposals
Rules Transformers
• https://www.drupal.org/node/2251267
NoFlo
UI Brainstorming
• https://www.drupal.org/node/2251267
Sprint with us!
• Port actions & conditions
• https://www.drupal.org/node/2245015
Sprint with us! - Tomorrow
• Port actions & conditions
• https://www.drupal.org/node/2245015
Sprint with us!
• Port actions & conditions
• https://www.drupal.org/node/2245015
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”
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
• hook_rules_plugin_info
-> Implement a RulesExpression plugin
Provide input evaluators
• hook_rules_evaluator_info
-> Implement a RulesDataProcessor plugin
/**
* 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
• RulesUnitTestBase extends UnitTestCase
• Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, …
Automated testing
• RulesUnitTestBase extends UnitTestCase
• Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, …
• RulesIntegrationTestBase
• use ActionManager, ConditionManager,
TypedDataManager, …
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
• RulesUnitTestBase extends UnitTestCase
• Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, …
• RulesIntegrationTestBase
• use ActionManager, ConditionManager,
TypedDataManager, …
Automated testing
• RulesUnitTestBase extends UnitTestCase
• Internal unit tests such as RuleTest, RulesAndTest,
RulesContextTraitTest, …
• RulesIntegrationTestBase
• use ActionManager, ConditionManager,
TypedDataManager, …
• RulesEntityIntegrationTestBase
/**
* @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('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());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* 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());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* 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());
}
}
class EntityIsOfTypeTest extends RulesEntityIntegrationTestBase {
/**
* 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());
}
}
RulesState
RulesState
• variables
• applyDataSelector
• autoSave
Traits
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, …);
}
}
class MyClass {
use StringTranslationTrait;
public function __construct(
TranslationInterface $string_translation) {
$this->stringTranslation = $string_translation;
}
/**
* Does something.
*/
public function doSth() {
// ...
$string = $this->t('Something');
// ...
}
}
Traits
• RulesContextTrait
• in addition to ContextAwarePluginBase
• used in RulesActionBase and RulesConditionBase
Outro
Sprint with us!
• Port actions & conditions
• https://www.drupal.org/node/2245015
Sprint with us!
• Port actions & conditions
• https://www.drupal.org/node/2245015
https://www.youtube.com/watch?v=gEH291mq48Y

Web automation with #d8rules (European Drupal Days 2015)

  • 1.
    © Ibuildings 2014/2015- All rights reserved #DrupalDaysEU Web-automation in Drupal 8 with
  • 2.
    #DrupalDaysEU © Ibuildings 2014/2015- All rights reserved Gold Sponsors
  • 3.
    #DrupalDaysEU © Ibuildings 2014/2015- All rights reserved Media Sponsors Silver Sponsors
  • 4.
    Wolfgang Ziegler //fago Drupal Core: Entity & Forms Contrib: Entity API, Rules, Profile2, Field Collection, ... based in Vienna CEO - Head of Development @the_real_fago
  • 5.
  • 7.
    #d8rules team • fago(Rules creator) • klausi (co-maintainer) • fubhy (developer) • nico grienauer (design) • dasjo (communication)
  • 8.
  • 9.
  • 10.
  • 11.
    Rules • Build flexibleworkflows using events, condition & actions • Send customized mails to notify your users about updates • Create custom redirections, system messages, breadcrumbs
  • 12.
    Rules • Build flexibleworkflows using events, condition & actions • Send customized mails to notify your users about updates • Create custom redirections, system messages, breadcrumbs • 250.000+ reported installations (every 5th Drupal site) • Hundreds of integration modules • Entity API, Fields, Views, Webform, Context, Features, Search API, Tokens, Paths, Menus, Queue or Field formatter
  • 13.
  • 14.
    Drupal 8 wins •OOP • APIs • PHPUnit • Symfony2 • Removed legacy modules • Web services built in • Front-end, responsive, ...
  • 15.
    For developers • Drupal8 DX • Plug-in system • Complete Entity API • Deployable config via CMI
  • 16.
    Reusable components • ContextAPI shared w/ Core, Page Manager • Tokens (automatic based on typed data) • Typed data widgets & formatters • Embeddable Rules UI components • Actions & Conditions • Rules data selector for tokens, contexts
  • 17.
  • 18.
    For site builders •Admin UI usability improvements • Simple Views Bulk operations in core • “Inline Rules” instead of Rule sets / Rules conditional
  • 19.
  • 20.
    #d8rules goals • AccelerateDrupal 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.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
    Rules 8.x M1 Rulescore API fundamentals ➡ Rules core engine, plugin types ➡ Align Rules condition & action APIs with core ➡ Parameter configuration & Context mapping
  • 26.
    Rules 8.x M2 Rulescore completion • Complete Rules engine features • Rules plugins, part two (Events, Loops, ...) • Entity token support • Configuration entity, CMI and integrity (started!) • Generic Rules integrations
  • 27.
    Rules 8.x M3 Rulesrelease • Rules UI • Reusable UI components • Rules scheduler port • Port existing integration (started!)
  • 28.
    Sprints & trainings •DrupalCamp Alpe-Adria May 2014 • DrupalCon Austin June 2014 • Drupalaton August 2014 • DrupalCon Amsterdam September 2014 • Global Sprint Days January 2015
  • 29.
    Contribution • 50 forks,22 contributors • paranoik, stevepurkiss, scuts, jibran, chindris, omissis, ndewhurst, jzavrl, MegaChriz, bbujisic, dawehner, torenware, bartfeenstra, M1r1k, rinasek, joashuataylor, lokapujya, icanblink, ... • 132 closed pull requests, Thank you all!
  • 30.
    Getting started • SetupDrupal 8 • Fork Rules 8.x on github: • https://github.com/fago/rules
  • 32.
  • 33.
  • 34.
    Describe data toRules • hook_rules_data_info() -> ? • hook_entity_property_info_alter -> ?
  • 35.
    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)
  • 36.
    Data types • any •string, integer, uri, float, ... • email • timestamp, datetime_iso8601 • timespan, duration_iso8601 • entity • entity:node • entity:comment • field_item • field_item:string • field_item:text • field_item:image
  • 37.
    /** * The floatdata 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 Float extends PrimitiveBase implements FloatInterface { /** * {@inheritdoc} */ public function getCastedValue() { return (float) $this->value; } }
  • 38.
    /** * The floatdata 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 Float extends PrimitiveBase implements FloatInterface { /** * {@inheritdoc} */ public function getCastedValue() { return (float) $this->value; } }
  • 39.
    /** * Plugin implementationof 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')); $properties['title'] = DataDefinition::create('string') ->setLabel(t('Link text’)); … return $properties;
  • 40.
    /** * Plugin implementationof 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')); $properties['title'] = DataDefinition::create('string') ->setLabel(t('Link text’)); … return $properties;
  • 41.
    Describe data torules • hook_rules_data_info() -> ? • hook_entity_property_info_alter -> ?
  • 42.
    Describe data torules • 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()
  • 43.
    Every content entity & field type inDrupal 8 is supported by Rules out-of-the box!
  • 44.
  • 45.
    Plug-ins • OOP • Annotations •Auto-loading • Discovery • Derivatives
  • 46.
  • 47.
  • 48.
    /** * 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(); } }
  • 49.
  • 50.
    /** * 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(); } }
  • 51.
  • 52.
    Provide events • hook_rules_event_info ->Implement an Event plug-in (not implemented, yet)
  • 53.
  • 54.
    Context • Specifying thecontext of a plugin
  • 55.
    /** * 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(); } }
  • 56.
    /** * 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 { /** * {@inheritdoc} */ public function execute() { $entity = $this->getContextValue('entity'); $entity->delete();
  • 57.
    Context • Specifying thecontext of a plugin
  • 58.
  • 59.
    class DataListItemRemove extendsRulesActionBase { /** * {@inheritdoc} */ public function execute() { $list = $this->getContextValue('list'); $item = $this->getContextValue('item'); foreach (array_keys($list, $item) as $key) { unset($list[$key]); } $this->setContextValue('list', $list); } }
  • 60.
    class DataListItemRemove extendsRulesActionBase { /** * {@inheritdoc} */ public function execute() { $list = $this->getContextValue('list'); $item = $this->getContextValue('item'); foreach (array_keys($list, $item) as $key) { unset($list[$key]); } $this->setContextValue('list', $list); } }
  • 61.
    class DataListItemRemove extendsRulesActionBase { /** * {@inheritdoc} */ public function execute() { $list = $this->getContextValue('list'); $item = $this->getContextValue('item'); foreach (array_keys($list, $item) as $key) { unset($list[$key]); } $this->setContextValue('list', $list); } }
  • 62.
    class FetchEntityById extendsRulesActionBase implements ContainerFactoryPluginInterface { … /** * {@inheritdoc} */ public function execute() { $entity_type = $this->getContextValue('entity_type'); $entity_id = $this->getContextValue('entity_id'); $storage = $this->entityManager->getStorage($entity_type); $entity = $storage->load($entity_id); $this->setProvidedValue('entity', $entity); } }
  • 63.
    class FetchEntityById extendsRulesActionBase implements ContainerFactoryPluginInterface { … /** * {@inheritdoc} */ public function execute() { $entity_type = $this->getContextValue('entity_type'); $entity_id = $this->getContextValue('entity_id'); $storage = $this->entityManager->getStorage($entity_type); $entity = $storage->load($entity_id); $this->setProvidedValue('entity', $entity); } }
  • 64.
  • 65.
    Context • Specifying context •Using context • getting context • setting context • setting provided values
  • 66.
  • 67.
    Storing configuration • D7:Entity exportables • Rules: • RulesPlugin->export() • entity_var_json_export() • rules_import() • RulesEntityController->import()
  • 68.
    Storing configuration • D7:Entity exportables • Rules: • RulesPlugin->export() -> RulesExpression::getConfiguration() RulesEntityController->import() -> RulesExpression::setConfiguration()
  • 69.
    Drupal 8: CMI •Rules leverages the Config System • Two types of config entities: • Reaction Rules (todo) • Components (WIP) • Inherit features from CMI • Config deployment / import&export / sync • Config Translation (instead of Entity i18n) • Default config
  • 70.
    Provide default ruleconfigs • hook_default_rules_configuration() -> *.rules.yml via CMI
  • 71.
    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: ...
  • 72.
    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'
  • 73.
    Executing the component $config_entity= RulesComponent::load ('rules_test_default_component'); $expression = $config_entity ->getExpression(); $expression ->setContextValue('user', Drupal:: currentUser()) ->execute();
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 81.
    Sprint with us! •Port actions & conditions • https://www.drupal.org/node/2245015
  • 82.
    Sprint with us!- Tomorrow • Port actions & conditions • https://www.drupal.org/node/2245015
  • 83.
    Sprint with us! •Port actions & conditions • https://www.drupal.org/node/2245015
  • 85.
    Lists & Multiplevalues • list<foo> notation is gone • “list” data type & per data type class • Separate data definitions for lists vs. list items • Mark context as “multiple”
  • 86.
    Creating a Rulewith context
  • 87.
    $rule = $this->expressionManager->createRule([ 'context_definitions'=> [ 'test' => [ 'type' => 'string', 'label' => 'Test string', ], ], ]);
  • 88.
    $rule = $this->expressionManager->createRule([ 'context_definitions'=> [ 'test' => [ 'type' => 'string', 'label' => 'Test string', ], ], ]); $rule->addCondition('rules_test_string_condition', ContextConfig::create() ->map('text', 'test') );
  • 89.
    $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');
  • 90.
    $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');
  • 91.
    $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();
  • 92.
    Mapping required andprovided context
  • 93.
  • 94.
    $rule = $this->expressionManager->createRule(); //Condition provides a "provided_text" variable. $rule->addCondition('rules_test_provider');
  • 95.
    $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') );
  • 96.
    $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') );
  • 97.
    $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();
  • 98.
  • 99.
    Provide other Rulesplugins • hook_rules_plugin_info -> Implement a RulesExpression plugin
  • 100.
    Provide input evaluators •hook_rules_evaluator_info -> Implement a RulesDataProcessor plugin
  • 101.
    /** * A dataprocessor 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']; } }
  • 102.
  • 103.
    Automated testing • RulesUnitTestBaseextends UnitTestCase • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, …
  • 104.
    Automated testing • RulesUnitTestBaseextends UnitTestCase • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, … • RulesIntegrationTestBase • use ActionManager, ConditionManager, TypedDataManager, …
  • 105.
    class DataListCountIs extendsRulesConditionBase { /** * {@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; } } }
  • 106.
    class ListCountIsTest extendsRulesIntegrationTestBase { /** * 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()); } }
  • 107.
    Automated testing • RulesUnitTestBaseextends UnitTestCase • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, … • RulesIntegrationTestBase • use ActionManager, ConditionManager, TypedDataManager, …
  • 108.
    Automated testing • RulesUnitTestBaseextends UnitTestCase • Internal unit tests such as RuleTest, RulesAndTest, RulesContextTraitTest, … • RulesIntegrationTestBase • use ActionManager, ConditionManager, TypedDataManager, … • RulesEntityIntegrationTestBase
  • 109.
    /** * @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; }
  • 110.
    class EntityIsOfTypeTest extendsRulesEntityIntegrationTestBase { /** * 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()); } }
  • 111.
    class EntityIsOfTypeTest extendsRulesEntityIntegrationTestBase { /** * 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()); } }
  • 112.
    class EntityIsOfTypeTest extendsRulesEntityIntegrationTestBase { /** * 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()); } }
  • 113.
    class EntityIsOfTypeTest extendsRulesEntityIntegrationTestBase { /** * 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()); } }
  • 114.
  • 115.
  • 116.
  • 117.
    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, …); } }
  • 118.
    class MyClass { useStringTranslationTrait; public function __construct( TranslationInterface $string_translation) { $this->stringTranslation = $string_translation; } /** * Does something. */ public function doSth() { // ... $string = $this->t('Something'); // ... } }
  • 119.
    Traits • RulesContextTrait • inaddition to ContextAwarePluginBase • used in RulesActionBase and RulesConditionBase
  • 120.
  • 122.
    Sprint with us! •Port actions & conditions • https://www.drupal.org/node/2245015
  • 123.
    Sprint with us! •Port actions & conditions • https://www.drupal.org/node/2245015
  • 124.