Skip to content

Commit d980ce3

Browse files
committed
Merge branch '7.4' into 8.0
* 7.4: Minor tweak [Workflow] Add weighted transitions
2 parents c6ca90d + 014f02d commit d980ce3

File tree

1 file changed

+227
-0
lines changed

1 file changed

+227
-0
lines changed

workflow.rst

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,233 @@ when needed and vice-versa when working with your objects::
388388
// ...
389389
};
390390
391+
Using Weighted Transitions
392+
~~~~~~~~~~~~~~~~~~~~~~~~~~
393+
394+
.. versionadded:: 7.4
395+
396+
Support for weighted transitions was introduced in Symfony 7.4.
397+
398+
A key feature of workflows (as opposed to state machines) is that an object can
399+
be in multiple places simultaneously. For example, when building a product, you
400+
might assemble several components in parallel. However, in the previous example,
401+
each place could only record whether the object was there or not, like a binary flag.
402+
403+
**Weighted transitions** introduce multiplicity: a place can now track how many
404+
times an object is in that place. Technically, weighted transitions allow you to
405+
define transitions where multiple tokens (instances) are consumed from or produced
406+
to places. This is useful for modeling complex workflows such as manufacturing
407+
processes, resource allocation, or any scenario where multiple instances of something
408+
need to be produced or consumed.
409+
410+
For example, imagine a table-making workflow where you need to create 4 legs, 1 top,
411+
and track the process with a stopwatch. You can use weighted transitions to model this:
412+
413+
.. configuration-block::
414+
415+
.. code-block:: yaml
416+
417+
# config/packages/workflow.yaml
418+
framework:
419+
workflows:
420+
make_table:
421+
type: 'workflow'
422+
marking_store:
423+
type: 'method'
424+
property: 'marking'
425+
supports:
426+
- App\Entity\TableProject
427+
initial_marking: init
428+
places:
429+
- init
430+
- prepare_leg
431+
- prepare_top
432+
- stopwatch_running
433+
- leg_created
434+
- top_created
435+
- finished
436+
transitions:
437+
start:
438+
from: init
439+
to:
440+
- place: prepare_leg
441+
weight: 4
442+
- place: prepare_top
443+
weight: 1
444+
- place: stopwatch_running
445+
weight: 1
446+
build_leg:
447+
from: prepare_leg
448+
to: leg_created
449+
build_top:
450+
from: prepare_top
451+
to: top_created
452+
join:
453+
from:
454+
- place: leg_created
455+
weight: 4
456+
- top_created # weight defaults to 1
457+
- stopwatch_running
458+
to: finished
459+
460+
.. code-block:: xml
461+
462+
<!-- config/packages/workflow.xml -->
463+
<?xml version="1.0" encoding="UTF-8" ?>
464+
<container xmlns="http://symfony.com/schema/dic/services"
465+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
466+
xmlns:framework="http://symfony.com/schema/dic/symfony"
467+
xsi:schemaLocation="http://symfony.com/schema/dic/services
468+
https://symfony.com/schema/dic/services/services-1.0.xsd
469+
http://symfony.com/schema/dic/symfony
470+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
471+
472+
<framework:config>
473+
<framework:workflow name="make_table" type="workflow">
474+
<framework:marking-store type="method">
475+
<framework:argument>marking</framework:argument>
476+
</framework:marking-store>
477+
<framework:support>App\Entity\TableProject</framework:support>
478+
<framework:initial-marking>init</framework:initial-marking>
479+
480+
<framework:place>init</framework:place>
481+
<framework:place>prepare_leg</framework:place>
482+
<framework:place>prepare_top</framework:place>
483+
<framework:place>stopwatch_running</framework:place>
484+
<framework:place>leg_created</framework:place>
485+
<framework:place>top_created</framework:place>
486+
<framework:place>finished</framework:place>
487+
488+
<framework:transition name="start">
489+
<framework:from>init</framework:from>
490+
<framework:to weight="4">prepare_leg</framework:to>
491+
<framework:to weight="1">prepare_top</framework:to>
492+
<framework:to weight="1">stopwatch_running</framework:to>
493+
</framework:transition>
494+
<framework:transition name="build_leg">
495+
<framework:from>prepare_leg</framework:from>
496+
<framework:to>leg_created</framework:to>
497+
</framework:transition>
498+
<framework:transition name="build_top">
499+
<framework:from>prepare_top</framework:from>
500+
<framework:to>top_created</framework:to>
501+
</framework:transition>
502+
<framework:transition name="join">
503+
<framework:from weight="4">leg_created</framework:from>
504+
<framework:from>top_created</framework:from>
505+
<framework:from>stopwatch_running</framework:from>
506+
<framework:to>finished</framework:to>
507+
</framework:transition>
508+
</framework:workflow>
509+
</framework:config>
510+
</container>
511+
512+
.. code-block:: php
513+
514+
// config/packages/workflow.php
515+
use App\Entity\TableProject;
516+
use Symfony\Config\FrameworkConfig;
517+
518+
return static function (FrameworkConfig $framework): void {
519+
$makeTable = $framework->workflows()->workflows('make_table');
520+
$makeTable
521+
->type('workflow')
522+
->supports([TableProject::class])
523+
->initialMarking(['init']);
524+
525+
$makeTable->markingStore()
526+
->type('method')
527+
->property('marking');
528+
529+
$makeTable->place()->name('init');
530+
$makeTable->place()->name('prepare_leg');
531+
$makeTable->place()->name('prepare_top');
532+
$makeTable->place()->name('stopwatch_running');
533+
$makeTable->place()->name('leg_created');
534+
$makeTable->place()->name('top_created');
535+
$makeTable->place()->name('finished');
536+
537+
$makeTable->transition()
538+
->name('start')
539+
->from(['init'])
540+
->to([
541+
['place' => 'prepare_leg', 'weight' => 4],
542+
['place' => 'prepare_top', 'weight' => 1],
543+
['place' => 'stopwatch_running', 'weight' => 1],
544+
]);
545+
546+
$makeTable->transition()
547+
->name('build_leg')
548+
->from(['prepare_leg'])
549+
->to(['leg_created']);
550+
551+
$makeTable->transition()
552+
->name('build_top')
553+
->from(['prepare_top'])
554+
->to(['top_created']);
555+
556+
$makeTable->transition()
557+
->name('join')
558+
->from([
559+
['place' => 'leg_created', 'weight' => 4],
560+
'top_created', // weight defaults to 1
561+
'stopwatch_running',
562+
])
563+
->to(['finished']);
564+
};
565+
566+
In this example, when the ``start`` transition is applied, it creates 4 tokens in
567+
the ``prepare_leg`` place, 1 token in ``prepare_top``, and 1 token in
568+
``stopwatch_running``. Then, the ``build_leg`` transition must be applied 4 times
569+
(once for each token), and the ``build_top`` transition once. Finally, the ``join``
570+
transition can only be applied when all 4 legs are created, the top is created,
571+
and the stopwatch is still running.
572+
573+
Weighted transitions can also be defined programmatically using the
574+
:class:`Symfony\\Component\\Workflow\\Arc` class::
575+
576+
use Symfony\Component\Workflow\Arc;
577+
use Symfony\Component\Workflow\Definition;
578+
use Symfony\Component\Workflow\Transition;
579+
use Symfony\Component\Workflow\Workflow;
580+
581+
$definition = new Definition(
582+
['init', 'prepare_leg', 'prepare_top', 'stopwatch_running', 'leg_created', 'top_created', 'finished'],
583+
[
584+
new Transition('start', 'init', [
585+
new Arc('prepare_leg', 4),
586+
new Arc('prepare_top', 1),
587+
'stopwatch_running', // defaults to weight 1
588+
]),
589+
new Transition('build_leg', 'prepare_leg', 'leg_created'),
590+
new Transition('build_top', 'prepare_top', 'top_created'),
591+
new Transition('join', [
592+
new Arc('leg_created', 4),
593+
'top_created',
594+
'stopwatch_running',
595+
], 'finished'),
596+
]
597+
);
598+
599+
$workflow = new Workflow($definition);
600+
$workflow->apply($subject, 'start');
601+
602+
// Build each leg (4 times)
603+
$workflow->apply($subject, 'build_leg');
604+
$workflow->apply($subject, 'build_leg');
605+
$workflow->apply($subject, 'build_leg');
606+
$workflow->apply($subject, 'build_leg');
607+
608+
// Build the top
609+
$workflow->apply($subject, 'build_top');
610+
611+
// Now we can join all parts
612+
$workflow->apply($subject, 'join');
613+
614+
The ``Arc`` class takes two parameters: the place name and the weight (which must be
615+
greater than or equal to 1). When a place is specified as a simple string instead of
616+
an ``Arc`` object, it defaults to a weight of 1.
617+
391618
Using a multiple state marking store
392619
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
393620

0 commit comments

Comments
 (0)