Skip to content

Action tasks

The optional Orchestra Action submodule (orchestra_action) adds an action task: an automated step whose work is a Drupal Action plugin. It is Orchestra's answer to Maestro's "batch function" task (a step that runs a named, automated operation rather than waiting for a human), built on Drupal's own action system instead of a bare function name.

Why an Action plugin, not a callable string

Maestro's batch task stores a PHP function name in configuration and calls it. Orchestra deliberately does not offer an arbitrary-callable field: a workflow is config (exportable, importable, shippable as a recipe), so a literal callable would turn "edit a workflow" into "run arbitrary PHP." A Drupal Action plugin is the safe, idiomatic equivalent: discoverable, configurable and access-aware, with its body in a plugin class. For logic that is purely about process flow, a custom task type remains the cleaner path, since it receives the token and instance directly.

The action task

A node of type action runs the configured action and then advances, with no parking and no inbox. Three kinds of action fit:

  • an entity action (one that names an entity type, like Publish or Delete) runs on a resolved target entity;
  • a system action (one with no entity type, like Send email) runs as-is;
  • a context-aware action (see below) is additionally handed the running process, so it can read and write process variables.
flowchart LR
  s([Start]) --> a["Notify owner<br/>type: action<br/>action: Send email"]
  a --> g{outcome?}
  g -->|success| d([Done])
  g -->|failure| r["Escalate<br/>type: action"]

Node config (in the Complete modeler or in YAML):

n_notify:
  type: action
  config:
    action_id: action_send_email_action
    action_settings:
      recipient: '[node:author:mail]'
      subject: 'Your article is in review'
      message: 'A reviewer will look at it shortly.'
    payload_variable: notified
    completion_scope: instance
  • action_id: the action plugin to run.
  • action_settings: the action's own configuration, when it has any, typed by the action's own schema (action.configuration.<action id>), so Config Translation offers its texts, such as an email's subject and message. The node editor writes it for you; see Configuring the action.
  • payload_variable / completion_scope: see Success and failure.
  • target_variable: see Choosing the entity.

Choosing the entity

An entity action needs something to act on, and the action task takes it from the process variable named by target_variable. The entity type is taken from the action, so the variable chooses only which entity of that type. The action then runs on whichever one it names, with full privilege and no further check, because an automated step has no actor to check against: point this only at a variable the process itself computes, never at one an untrusted party can set, a webform field or an interaction payload, or they choose the entity a privileged action runs on.

To act on the content the run is attached to, use the content_action task instead, which orchestra_content_action ships. It is the same step with the run's attachment as its source, so nothing outside the model chooses what it acts on; an attachment_key picks which attached entity when the run carries several, and the empty key is the primary subject.

A system action takes no entity and ignores both. If an entity action cannot resolve one, the step fails (see below).

Success and failure

The step is automated, so it runs and the token advances. With a payload_variable configured, the task catches a failing action and sets the variable to failure (and to success otherwise), so an outgoing flow condition can branch to an error path, the equivalent of Maestro's task completion status. Without a payload variable, a failure propagates and fails the process (the engine's default), so a misconfiguration is never swallowed.

Configuring the action

When the chosen action has its own settings (a configurable action such as Send email), its settings form is embedded in the node editor and follows the action selected right now: pick a different action and its own settings replace the previous one's, which start from that action's defaults. A non-configurable action (Publish, Unpublish, Delete) needs none.

BPMN.io

In the diagram editor a node's properties are plain text fields, so the action's settings show as the raw JSON string and are best edited in the accessible form modeler (orchestra_cm), which embeds the action's own form; action_id and the simple keys work in either.

Context-aware actions

A plain Action plugin's execute() receives only the entity, so it cannot reach the process. An action that needs the process (to read or write a variable, or to know which instance it runs in) implements Drupal\orchestra_engine\OrchestraContextAwareInterface:

public function setProcessContext(ProcessInstanceInterface $instance, TokenInterface $token): void;

The action task injects the instance and token before running such an action, giving it the same reach a Maestro batch function has from its process and queue IDs. This interface lives in the kernel, so it is the general seam for any foreign plugin Orchestra later runs as automated work; an action that needs no context simply does not implement it.

Acting on something else

The two tasks differ in one thing only: where the entity comes from. Everything else, from instantiating the action to the success and failure contract, lives in ActionTaskBase (orchestra_action), which is @api and exists to be extended. orchestra_content_action is what that looks like in practice.

A module with its own idea of what a step should act on ships a task type of its own, extending the base and answering two questions:

final class MyActionTask extends ActionTaskBase {

  protected function buildTargetElements(FormStateInterface $form_state): array {
    // The settings that say what to act on, merged in after the action select.
  }

  protected function resolveEntity(string $entity_type_id, ProcessInstanceInterface $instance, TokenInterface $token): ?EntityInterface {
    // The entity of that type, or NULL: the step then reports that it needed
    // one and could not resolve it.
  }

}

Add the settings the target needs to defaultConfiguration() and they are carried through the form, the modeler round-trip and storage with the rest; the base folds every key it declares back on submit.

Relationship to the moderation task

The moderation_transition task is a special case of an action task: setting an entity's moderation state is one specific automated operation. The action task is the generic form: any action, on the entity a variable names, with success/failure routing; its content_action sibling is the same step on the run's own attachment.