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.commentalongsidedecision.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¶
- An article is created; a content workflow is started attached to it
(
startFor($article, 'content_review')). - The token reaches the
entity_formreview node, parks, and a work item appears in the editors' inbox. - An editor opens it: the article's edit form renders, with Save and approve / Save and reject buttons.
- They fix a typo and press Save and approve; the article is saved and the
task completes with
approved;decisionis set toapproved. - 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_ecasubmodule 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).