drupal-eca-recipe / eca_lib_0002
Demonstrates how conditions are combined in ECA, where a successor link carries at most one condition and every combination is therefore built from the arrangement of links. The model shows the three idioms that follow from that constraint. An AND is chained through a pass-through action, an OR is d
Package info
gitlab.lakedrops.com/drupal/recipes/eca_lib_0002
Type:drupal-recipe
pkg:composer/drupal-eca-recipe/eca_lib_0002
Requires
- drupal/eca: *
- drupal/modeler_api: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
ID: eca_lib_0002
ECA has no AND operator and no OR operator. It has one rule about links, and every way of combining conditions follows from that rule. A successor link carries at most one condition. In the stored configuration, condition holds a single component ID rather than a list, so there is no second slot to put another condition into.
Everything below is built on top of that constraint. The model reads three values, evaluates five conditions, and reports which of three independent rules matched. Each rule uses a different combination idiom, so this is really three worked examples sharing one set of inputs.
Running the model
The model starts from a custom event, which means it needs no content, no form, and no browser. Set the three inputs, then trigger it.
drush state:set eca_lib_0002_channel web
drush state:set eca_lib_0002_amount 250
drush state:set eca_lib_0002_vip 0
drush eca:trigger:custom_event eca_lib_0002
The inputs deliberately live outside the model, in Drupal's persistent state. Three eca_keyvaluestore_read actions load them into the tokens [channel], [amount], and [vip] before any condition runs. Keeping them outside is what makes the model worth running more than once. Change one value, trigger again, and watch a different set of paths report.
Those read actions use the collection state, which is Drupal core's own state, the store that drush state:set writes to. ECA also ships an action called eca_state_read, labeled as a persistent state read, and that one is not the same thing. It reads a private key value collection belonging to ECA, so a value placed with drush state:set is invisible to it. Use eca_keyvaluestore_read with the collection state whenever you want a value that the command line can set.
AND, by chaining through a pass-through action
Because a link carries one condition, an AND needs a second link to hang the second condition on. ECA ships an action for exactly this. Chain action for AND condition takes no configuration and does nothing at all. Its only job is to be a node in the middle, so that another conditioned link can leave it.
Path A is that shape. One link leaves Report the inputs carrying "channel is web" and reaches the AND action. One link leaves the AND action carrying "amount is at least 100" and reaches the message. The message runs only when both conditions returned TRUE, because a link whose condition is FALSE skips its target and everything behind it. Add one more AND action for every further condition you need.
OR, by drawing several links into one target
An OR needs no helper action. Draw one link per condition from the same source to the same target. Any single link whose condition returns TRUE is enough to run the shared target.
Path B is that shape. Two links leave Report the inputs, one carrying "channel is phone" and one carrying "channel is partner", and both point at the same message action.
ECA walks every outgoing link rather than stopping at the first match, so it is fair to ask what happens when two of them are true at once. Within one predecessor, nothing surprising happens. ECA keeps a list of the successors it has already executed for that predecessor and refuses to execute the same one twice, logging Prevent duplicate execution when it declines. The guard is scoped to a single predecessor, though. If two different actions both link to the same target, that target does run twice, once per predecessor, and the guard never sees the second call.
Nesting the two into (A or B) and (C or D)
The two idioms compose, and composing them is the point of this model. Fan several conditioned links into an AND action to get the left side of the expression, then fan several conditioned links out of it to get the right side.
Path C is that shape. Two links leave Report the inputs carrying "channel is phone" and "channel is partner" and both reach the same AND action, which is the (phone or partner) half. Two links leave that AND action carrying "amount is at least 100" and "vip flag is 1" and both reach the same message, which is the (at least 100 or vip) half. The message runs when one condition from each half is true.
What each input set produces
Four input sets cover the interesting behavior of the model.
| channel | amount | vip | Reported |
|---|---|---|---|
web | 250 | 0 | the inputs, then path A |
web | 50 | 0 | the inputs only |
phone | 20 | 0 | the inputs, then path B |
partner | 500 | 1 | the inputs, then path B, then path C |
The second row is the one to dwell on. Path A does not report, because the AND chain stopped at its second link. The Report the inputs message still appears, which is how you tell a model that ran and matched nothing from a model that never ran at all.
The fourth row is the other one to dwell on. Two independent rules matched the same input, and both reported. Rules in ECA do not compete, and nothing makes them exclusive unless you make them exclusive yourself.
Gateways are not a branching primitive
It is tempting to reach for a gateway to express a choice. Do not. ECA's configuration schema restricts gateways.*.type to the single value 0, and a gateway passes control to all of its successors, subject only to the condition on each outgoing link. Despite the "exclusive gateway" label that modelers show, the behavior is inclusive. A gateway is useful for merging flows and for keeping a diagram readable, and it adds no logic of its own. The combining is done by the links, exactly as described above.
Three things that will otherwise cost you an hour
Drupal's messenger drops a repeated identical string. Every branch in this model therefore reports a different sentence. If you copy this model and give two branches the same message text, one of them will appear to have been skipped when it actually ran. Give each outcome its own wording, or read the ECA log instead of the messages.
After applying the recipe, run drush eca:subscriber:rebuild. ECA listens only to the events named in a cached list of enabled models, and an import that leaves that list stale produces a model that looks perfect in the interface and never fires. The command is cheap and idempotent, so run it after every import rather than working out whether this one needed it.
Triggered from Drush, the model runs as the anonymous user. Nothing here depends on that, because the model touches no entities and checks no access. It matters as soon as you extend the model, so keep it in mind before concluding that a newly added action is broken.
Installation
## Import recipe
composer require drupal-eca-recipe/eca_lib_0002
# Apply recipe with Drush (requires version 13 or later):
drush recipe ../recipes/eca_lib_0002
# Apply recipe without Drush:
cd web && php core/scripts/drupal recipe ../recipes/eca_lib_0002
# Rebuilding caches is optional, sometimes required:
drush cr