Home / Symfony / New in Symfony 7.4: Weighted Workflow Transitions

New in Symfony 7.4: Weighted Workflow Transitions


Grégoire Pineau
Contributed by
Grégoire Pineau
in
#60201

Symfony’s Workflow component models business processes as a series of places
(states or steps) connected by transitions (actions that move from one place
to another). An object progresses through the workflow, and its current position
is tracked by the marking, which stores which place(s) the object is in.

A key feature of workflows (as opposed to state machines) is that an object can
be in multiple places simultaneously. For example, when building a product,
you might assemble several components in parallel. However, until now, each
place could only record whether the object was there or not, like a binary flag.

Symfony 7.4 introduces multiplicity thanks to weighted transitions: a place
can now track how many times an object is in that place. This is useful when
you need multiple instances of something before proceeding. For example,
“collect 4 legs before assembling the table” or “wait for 3 approvals before publishing”.

How Weighted Transitions Work

You can assign a weight to specify how many instances are produced or required:

  • Weight on output (to): the transition places the object in the target
    place N times;
  • Weight on input (from): the transition requires the object to be in the
    source place N times before it can fire.

Without weights, every transition produces or consumes exactly one instance per place.

Example: Building a Table

Consider a workflow that models table construction. A table needs four legs,
one tabletop, and we also want to track timing. Here’s how weighted transitions
handle this:

# config/packages/workflow.yaml
framework:
    workflows:
        make_table:
            transitions:
                start:
                    from: init
                    to:
                        - place: prepare_leg
                          weight: 4
                        - place: prepare_top
                          weight: 1
                        - place: stopwatch_running
                          weight: 1
                build_leg:
                    from: prepare_leg
                    to: leg_created
                build_top:
                    from: prepare_top
                    to: top_created
                join:
                    from:
                        - place: leg_created
                          weight: 4
                        - top_created
                        - stopwatch_running
                    to: finished

When using the following code:

$subject = new Subject();
$workflow = new Workflow($definition);

$workflow->apply($subject, 'start');

$workflow->apply($subject, 'build_leg');
$workflow->apply($subject, 'build_top');
$workflow->apply($subject, 'build_leg');
$workflow->apply($subject, 'build_leg');
$workflow->apply($subject, 'build_leg');
$workflow->apply($subject, 'join');

This is how the execution flow works:

  1. Firing start marks the object as being in prepare_leg four times, in
    prepare_top once, and in stopwatch_running once;
  2. Each time you fire build_leg, it consumes one instance from prepare_leg
    and adds one to leg_created (fire it four times to accumulate four instances);
  3. Firing build_top creates the single tabletop;
  4. The join transition requires the object to be in leg_created exactly
    four times, plus once in each of the other places, before it can fire.

This creates a synchronization point: you cannot proceed to assembly until
all required components reach their specified quantities. The workflow enforces
these structural constraints at the model level.


Sponsor the Symfony project.
Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *