@@ -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+
391618Using a multiple state marking store
392619~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
393620
0 commit comments