Skip to content

Symfony Workflow Component - in some cases event "workflow.[workflow name].entered.[place name]" is being dispatched multiple times for a place even though marking was already in that place #57924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
m4y4-dev opened this issue Aug 5, 2024 · 6 comments · Fixed by #60194

Comments

@m4y4-dev
Copy link

m4y4-dev commented Aug 5, 2024

Symfony version(s) affected

from 5.2 until 7.2

Description

When working with a workflow that uses two or more places at the same time the "workflow.[workflow name].entered.[place name]" events are being dispatched more than once (when the workflow actually entered a place). At the moment, this events are always dispatched (for all the places, that workflow is currently at) when any place is entered, which in my opinion is wrong and causes problems in workflow executions because listeners for "workflow.[workflow name].entered.[place name]" events get executed more than once.

For example
The marking of workflow "reproduce_bug" just got to ['A' => 1, 'B' => 1] via a transition.
Then the events "workflow.reproduce_bug.entered.A" and "workflow.reproduce_bug.entered.B" where dispatched.
Everything is fine so far.
In the next trasition the marking gets changed from ['A' => 1, 'B' => 1] to ['A' => 1, 'D' => 1] via a transition.
Now the events "workflow.reproduce_bug.entered.D" and "workflow.reproduce_bug.entered.A" are being dispatched.
The event "workflow.reproduce_bug.entered.A" was dispatched again although the marking of the workflow was already at that place and it wasn't changed by the transition (as you can see in the example config below).

This behavior occures since version 5.2 of the Symfony Workflow Component up to current version 7.2.

How to reproduce

Ensure symfony/workflow component is installed
Could be any version >= 5.2 up to 7.2
composer require symfony/workflow:7.2

Example workflow configuration

framework:
  workflows:
    reproduce_bug:
      type: workflow
      marking_store:
        type: method
        property: state
      supports:
        - App\WorkflowState
      initial_marking: initial_place
      places:
        - initial_place
        - A
        - B
        - C
        - D
        - E
      transitions:
        from_initial_place_to_a_and_b:
          from: initial_place
          to: 
            - A
            - B
        from_a_to_c:
          from: A
          to: C
        from_b_to_d:
          from: B
          to: D

The App\WorkflowState entity

<?php

namespace App;

class WorkflowState
{
    protected $state;

    public function setState($state)
    {
        $this->state = $state;

        return $this;
    }

    public function getState()
    {
        return $this->state;
    }
}

Simple symfony command to execute the workflows transition

<?php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use App\WorkflowState;
use Symfony\Component\Workflow\Registry as WorkflowRegistry;

class ReproduceWorkflowBugCommand extends Command
{
    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('ReproduceWorkflowBugCommand')
            ->setDescription('')
        ;
    }

    /**
     * @var Symfony\Component\Workflow\Registry
     */
    protected $workflowRegistry;

    /**
     * @param Symfony\Component\Workflow\Registry $workflowRegistry
     */
    public function __construct(WorkflowRegistry $workflowRegistry)
    {
        parent::__construct();

        $this->workflowRegistry = $workflowRegistry;
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     * 
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $workflowState = new WorkflowState();

        $workflow = $this->workflowRegistry->get($workflowState, 'reproduce_bug');

        $workflow->apply($workflowState, 'from_initial_place_to_a_and_b');
        $workflow->apply($workflowState, 'from_b_to_d');
        $workflow->apply($workflowState, 'from_a_to_c');

        return 0;
    }
}

A simple event subscriber for debugging

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class TestEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'workflow.reproduce_bug.entered.A' => 'triggerOnWorkflowReproduceBugEnteredAEvent',
        ];
    }
    
    public function triggerOnWorkflowReproduceBugEnteredAEvent($event)
    {
        $transition = $event->getTransition();
        $marking = $event->getMarking();

        dump([
            'name' => $transition->getName(),
            'froms' => $transition->getFroms(),
            'tos' => $transition->getTos(),
            'marking' => $marking->getPlaces(),
        ]);
    }
}

When executing the command
bin/console ReproduceWorkflowBugCommand

Following output is shown

array:4 [
  "name" => "from_initial_place_to_a_and_b"
  "froms" => array:1 [
    0 => "initial_place"
  ]
  "tos" => array:2 [
    0 => "A"
    1 => "B"
  ]
  "marking" => array:2 [
    "A" => 1
    "B" => 1
  ]
]
array:4 [
  "name" => "from_b_to_d"
  "froms" => array:1 [
    0 => "B"
  ]
  "tos" => array:1 [
    0 => "D"
  ]
  "marking" => array:2 [
    "A" => 1
    "D" => 1
  ]
]

This output shows that the "workflow.reproduce_bug.entered.A" event was dispatched twice.
Once for transition "from_initial_place_to_a_and_b" which is expected and once for transition "from_b_to_d" which wasn't expected.

Possible Solution

The issue is caused within the entered method of the Symfony\Component\Workflow\Workflow class as it dispatches the "workflow.[workflow name].entered.[place name]" events for the current marking places of the workflow.

Code, wich causes the issue
vendor/symfony/workflow/Workflow.php

private function entered(...)
{
    ...
    foreach ($marking->getPlaces() as $placeName => $nbToken) {
        $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName));
    }
}

Fix
Instead of dispatching the events for each place of the marking, dispatch the events only for the "tos" of the transition. It was actually done this way until version 5.1 of the symfony workflow component.

if (null !== $transition) {
    foreach ($transition->getTos() as $placeName) {
        $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName));
    }
}

Additional Context

No response

@m4y4-dev
Copy link
Author

This issue really is causing a problem for me.
How can i help to improve the processing time on this issue?

@xabbuh
Copy link
Member

xabbuh commented Aug 22, 2024

Can you create a small example application that allows to reproduce your issue?

@m4y4-dev
Copy link
Author

m4y4-dev commented Aug 22, 2024

Hi @xabbuh,
thanks for the reply.

The above code examples describe the steps to reproduce the issue.
Here's also a git repository that includes a fresh symfony installation + the code to reproduce the issue: https://github.com/m4y4-dev/symfony-workflow-bug

You can either call the command to view the debug dump
bin/console ReproduceWorkflowBugCommand
Or view it via a browser by opening "/" url.

The App\EventSubscriber\TestEventSubscriber makes the issue visible by dumping some informations about the workflow event on "workflow.reproduce_bug.entered.A" events. As written above in the issue description, the dump gets event subscriber gets executed twice but should be executed only once.

Let me know, if you need a docker-compose to easily run the application.

@m4y4-dev
Copy link
Author

Hi, have you had the chance to look into that?
Is there something else i can provide to improve the processing time on this issue?

@lyrixx
Copy link
Member

lyrixx commented Apr 10, 2025

Thanks for this very details bug report ! 💛

I opened a PR to fix my mistake

nicolas-grekas added a commit that referenced this issue Apr 14, 2025
…is already in this marking (lyrixx)

This PR was merged into the 6.4 branch.

Discussion
----------

[Workflow] Fix dispatch of entered event when the subject is already in this marking

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #57924
| License       | MIT

This was wrong: #37813

Commits
-------

7db98c9 [Workflow] Fix dispatch of entered event when the subject is already in this marking
@m4y4-dev
Copy link
Author

Thanks for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants