The symfony branch uses the Workflow component.
Install with
composer install
bin/console doctrine:database:create
bin/console doctrine:schema:update --force
Make sure your database DSN is correct in .env
. (Override them if you like by creating .env.local
)
This "Notes" section is just an FYI. Start with Exercise 1 now.
If you update the workflow configuration, you will need to regenerate the SVG by running the following command:
# For the traffic light
bin/console app:build:svg state_machine.traffic_light
# For the article
bin/console app:build:svg workflow.article
To be able to dump workflows you need to install Graphwiz
If you update your models (files in App\Entity
) you need to update your database.
That is done with DoctrineMigrations.
# Create migration files:
bin/console doctrine:migration:diff
# Execute the files
bin/console doctrine:migration:migrate
Configure a new state machine using a new App\Workflow\TrafficLightFactory
. This class
should be used as a service factory.
You should create a new service named state_machine.traffic_light_php
using this factory.
state_machine.traffic_light_php:
public: true
class: Symfony\Component\Workflow\StateMachine
factory: [App\Workflow\TrafficLightFactory, create]
Create a workflow that looks like this:
The this section of Symfony documentation might be helpful here.
Do the same thing as Exercise 1 but use Symfony Yaml config.
framework:
workflows:
traffic_light:
# ...
The this section of Symfony documentation might be helpful here.
Note: Instead of Yaml you may use the PHP ConfigBuilder:
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework) {
$trafficLight = $framework->workflows()->workflows('traffic_light');
// ...
}
Dump the state machines to make sure the configuration is correct. Use one of the following commands:
(replace "NAME" with the name of your workflow/state machine)
php bin/console workflow:dump NAME | dot -Tpng -o workflow.png
php bin/console workflow:dump NAME --dump-format=puml | java -jar plantuml.jar -p > workflow.png
Example:
bin/console workflow:dump traffic_light_php | dot -Tpng -o php.png
bin/console workflow:dump traffic_light | dot -Tpng -o yaml.png
Now look at the file just created file "php.png" and "yaml.png.
Implement an event subscriber for your state machine that logs all transitions.
See it live:
symfony serve
# or
# php -S 127.0.0.1:8000 -t public
- Go to: http://127.0.0.1:8000/
- Select "TrafficLight"
- Create a TrafficLight object
- Move around the states using the buttons.
Implement an event subscriber that blocks an transition. Make sure only admins (ROLE_ADMIN) can execute that transition.
Take a look at the PullRequest
class and the PullRequestController
. It is
clearly a workflow here.
- Try to draw that workflow on a paper?
- Make a change to your workflow
- Could you make that same change to the code?
- (optional) Refactor the
PullRequest
class and thePullRequestController
to make it use a state machine or a workflow. - (optional) Try to apply the same change (from bullet 2.) to your new implementation.
Look at the SignupController
. It is currently 4 steps to signup. That is a business
requirement so we cannot change that. The business requires you to change the order
of the steps.
- See how the signup curretly works by visiting: http://127.0.0.1:8000/signup/start
- Refactor the
SignupController
to use a workflow - Make sure we cannot "skip" a step by hacking the URLs
- Make sure we use the workflow for
redirectToRoute
. (Use metadata on the workflow)
Example of using metadata:
framework:
workflows:
signup:
# ....
places:
name:
metadata:
route: signup_name
We now know our way around the Workflow component and all the events it is dispatching. On a real application this could quickly grow very big. If you are using the Messenger component you may find it strange that there are two kinds of "event dispatchers". One set of events and listeners for the workflow and one set for the Messenger.
To make our application less complex we decided to implement a custom EventDispatcher for our Workflow. That dispatcher should forward some events to a message bus and then ignore the rest.
- Run
composer req messenger
- Create
App\Workflow\EventDispatcher
- How to inject this event dispatcher to our existing
state_machine.traffic_light
? - How do we handle guard events? Why are they a problem?
A defensive programmer would say that a workflow is no place for adding metadata. That is outside the concern of the workflow and should be handled elsewhere.
Do Exercise 7 again but without metadata. You may introduce a new service which will give you the route for a specific state. Try to make that class reusable for multiple services.
Create a Guard event listener. In that listener, make a HTTP call to somewhere. (In a real application you may make an HTTP call to your own API) Debug the application to see when this HTTP call is executed. Could we see any side effects here?
A Domain Driven Design enthusiast would like to use the workflow component but they want the states to be defined in their object. How can they go about to solve this?
You are working with a highly advanced CRM system. You want the users to define the workflow for different objects themselves. How can we create such system with the Symfony workflow component?
Symfony 4.3 introduced $context
on the apply()
function. So you can pass some data from
the caller of the apply()
to the objects setMarking()
function.
Your task is to implement that and make sure it works.
Question: Could you imagine how this context could be abused?
What does the AuditTrail
feature do? How does it work? Is that a feature of the Workflow component?
When should you use the Registry
class? When should you not use it?
What is a TransitionBlockerList
. How and when is it used? What issue does it solve?
What is the difference between the following events:
- workflow.enter
- workflow.entered
- workflow.completed
- workflow.announce
Events are dispatched in a specific order. Example:
- workflow.leave
- workflow.[name].leave
- workflow.[name].leave.[place]
It goes from generic to specific. Is the order important? Can we reverse the order? Should we reverse the order?