Skip to content

Content-bound tasks

The optional Orchestra Content submodule (orchestra_content) binds a process to a content entity and adds a task whose work is editing that entity. It closes the gap with Maestro's signature step: a process that is about an article, and a review step that opens that article's edit form.

How a process attaches to content

The engine kernel stays content-agnostic: it knows nothing about entities. Instead orchestra_content owns a small side entity, orchestra_attachment, that references the process instance and the entity (entity_type_id + entity_id). The binding is queryable both ways, and the kernel and the instance entity are untouched:

Question Resolved by
Which content is this run about? AttachmentManager::entityFor($instance)
Which runs are about this content? AttachmentManager::instancesFor($entity)

Start a process already bound to an entity with AttachmentManager::startFor($entity, $workflow_id).

Multiple attachments (keyed)

A process is rarely about just one thing: an order has a customer and an invoice, a publication has an author and an editor. So a run can have several entities attached, each under a key (the role it plays). Every call takes an optional key; the empty key is the primary subject and is what the single-argument calls use, so a one-entity workflow needs no key at all.

// Bind the order (primary), then the customer and the invoice under keys.
$attachments->attach($instance, $order);
$attachments->attach($instance, $customer, 'customer');
$attachments->attach($instance, $invoice, 'invoice');

$attachments->entityFor($instance);             // the order (primary)
$attachments->entityFor($instance, 'customer'); // the customer
$attachments->entitiesFor($instance);           // all three, keyed by key
$attachments->instancesFor($customer, 'customer'); // runs where it is the customer
$attachments->detach($instance, 'customer');    // unbind one key, run unaffected

A process binds one entity per key: attaching again under a key already bound re-points it at the new entity rather than adding a second row, and a different key coexists. detach() unbinds one key (the binding only; the run continues), and deleting the instance clears them all. The key is a short machine name (a role); a consumer that acts on "the attached entity" (the entity-edit task, the moderation task, the Action task's attached target, and the ECA actions) has an Attachment key setting to say which one, defaulting to the primary subject.

The entity-edit task

A node of type entity_form parks the token and creates a work item, just like a user task, so it reuses assignment, outcomes and the inbox. The difference is the work: opening the task hands the assignee to a single screen showing the attached entity's edit form with:

  • a plain Save: persists the edits but leaves the task open, so the reviewer can edit across several sittings before deciding;
  • an optional Comment: recorded with the decision (as decision.comment alongside decision.result);
  • one save-and-decide button per outcome: saves the entity and completes the task with that outcome (which the engine routes on), in one action.
flowchart LR
  c["Article created"] --> s([Start])
  s --> r["Review<br/>type: entity_form<br/>outcomes: approved, rejected"]
  r -->|approved| p([Publish])
  r -->|rejected| d([Send back])

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

n_review:
  type: entity_form
  config:
    outcomes: 'approved,rejected'
    result_variable: decision
    form_operation: edit
    assignee_roles: editor
  • outcomes / result_variable / result_scope / assignee_*: the same keys a user task uses; the chosen outcome is recorded and routed on by the outgoing flow conditions.
  • form_operation: which entity form (form mode) to open, e.g. edit.

The handoff target is built in: the inbox sends an entity_form task to the content-task route (/orchestra/content/task), which renders the attached entity's form. No handler_url to configure.

Same machinery as a user task

Because an entity_form task is a parking human task, everything the inbox offers applies: claim, reassign, pooled vs. targeted assignment, and escalation timers. Only the completion screen differs: an entity edit form instead of the abstract review form.

Worked example: edit then approve

  1. An article is created; a content workflow is started attached to it (startFor($article, 'content_review')).
  2. The token reaches the entity_form review node, parks, and a work item appears in the editors' inbox.
  3. An editor opens it: the article's edit form renders, with Save and approve / Save and reject buttons.
  4. They fix a typo and press Save and approve; the article is saved and the task completes with approved; decision is set to approved.
  5. The outgoing flow condition routes the token to the publish branch.

Driving the moderation state

The companion orchestra_content_moderation submodule (requires core Content Moderation) adds a moderation_transition task that sets the attached entity's moderation_state. Put it on the branch that should reach a state, so the graph expresses the mapping (approved → set published, rejected → set draft) with no mapping table. Orchestra is the authority on when the state changes, so it sets the state directly (a state the entity's moderation workflow defines; an entity that is not moderated is skipped).

Starting a process for an entity

The entity_form task edits the entity the process is attached to, so something has to start the process and bind it (deliberately not a global save-watcher). Two ways:

  • In code: AttachmentManager::startFor($entity, $workflow_id).
  • From ECA: the orchestra_content_eca submodule adds a "Start Orchestra process for this entity" action; wire it to an entity insert/update event (the event's own condition is the de-duplication) and creating the content starts and attaches the process, the "when to start" living in the model, where Maestro puts it. That submodule also adds a "load the process's attached entity" action, so a model reacting to an Orchestra event can act on the content (e.g. set its state via content_moderation's own ECA action).

A ready-to-run example

orchestra_content_moderation ships an example_content_review workflow (review the entity → approve publishes it, reject sends it back to draft), and orchestra_content_eca ships an example ECA model that starts that review when an article is created. Enable the modules to get both.

Status and follow-ups

Shipped: the attachment (one per key, several per run), the entity_form task and edit screen, the moderation_transition task (orchestra_content_moderation), and the ECA start/attach + entity-exposure actions with example configs (orchestra_content_eca).

For a generic automated step that runs any Drupal Action plugin (of which moderation_transition is a special case), see Action tasks (orchestra_action).