web automation with #d8rules (european drupal days 2015)
Post on 16-Jul-2015
105 Views
Preview:
TRANSCRIPT
Wolfgang Ziegler // fago
Drupal Core: Entity & FormsContrib: Entity API, Rules, Profile2, Field Collection, ...
based in Vienna
CEO - Head of Development
@the_real_fago
#d8rules team
• fago (Rules creator)• klausi (co-maintainer)• fubhy (developer)• nico grienauer (design)• dasjo (communication)
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 conditions
Drupal 8 wins
• OOP
• APIs
• PHPUnit
• Symfony2
• Removed legacy modules
• Web services built in
• Front-end, responsive, ...
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
For site builders
• Admin UI usability improvements
• Simple Views Bulk operations in core
• “Inline Rules” instead of Rule sets / Rules conditional
#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.
Rules 8.x M1Rules core API fundamentals
➡ Rules core engine, plugin types
➡ Align Rules condition & action APIs with core
➡ Parameter configuration & Context mapping
Rules 8.x M2Rules 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 M3Rules 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
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, ...
• 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()-> 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()
/** * 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(); }
}
/** * 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 = {…} * ) */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(); }
}
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
• getting context
• setting context
• setting provided values
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
langcode: enstatus: truedependencies: {}id: rules_test_default_componentlabel: Rules test default componentmodule: rulesdescription: 'Tests adding Rules component by default.'tag: 'test'core: 8.xexpression_id: rules_ruleconfiguration:
...
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();
Rules Transformers
• https://www.drupal.org/node/2251267
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”
$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();
$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();
/** * 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
• 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('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()); }}
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
• RulesContextTrait
• in addition to ContextAwarePluginBase
• used in RulesActionBase and RulesConditionBase
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
top related