web automation with #d8rules (european drupal days 2015)

124
© Ibuildings 2014/2015 - All rights reserved #DrupalDaysEU Web-automation in Drupal 8 with

Upload: ibuildings-italia

Post on 16-Jul-2015

105 views

Category:

Internet


3 download

TRANSCRIPT

© 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 & FormsContrib: 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 conditions

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.

Sponsors

Funding state

Rules 8.x development status

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

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: 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();

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('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

• variables

• applyDataSelector

• autoSave

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

• 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