, (*1)
State Workflow Bundle
, (*2)
, (*3)
Helping you implementing a complex yet easily maintainable workflow.
Keywords : State Design Pattern, Workflow, Finite State Machine, Symfony2, (*4)
, (*5)
Our StateWorkflow object is responsible for managing all your States
and their Transitions
for your given Entity
implementing HasStateInterface.
Every single State is a class implementing our StateInterface and is managing its own transitions., (*6)
Ubiquitous Language
-
State : an Entity finite state at a given time (ex:
Booking payed
, Quote cancelled
, etc..)
-
Transition : a transition between state A and state B (ex: Booking waiting for payment --
Send confirmation mail
-> Booking payed, etc..)
Pros
- All your workflow is described via classes
- Each State is responsible for its own transitions
- Each State Transition can contain logic (Log, Event Sourcing, Assertion, Send mail, etc..)
- States are Symfony2 services
- All your workflow can be easily Unit Tested
- Entity's current state can be easily stored in database (simple string)
- Workflow specification file can be generated from code base
Cons
- Each time you add a transition you have to modify your own interface extending our StateInterface implementation
- If you only need a Finite State Machine without logic in your transitions. You might prefer https://github.com/yohang/Finite
- Not really following the famous precept : "prefer composition over inheritance" ..
 Usage
$bookingWorkflow = $this->get('demo.booking_engine.state_workflow');
// Initialize entity state to booking workflow default state : incomplete
// `Booking::__construct` contains `$bookingWorkflow->getDefaultState()->initialize($this);`
$booking = new Booking($bookingWorkflow, 200);
// Set incomplete Booking as paid
// Take care of the state transition (incomplete -> paid) - Send confirmation mail
$booking->getState($bookingWorkflow)
->setBookingAsPaid($booking);
// Get current booking state : StatePaid
$currentState = $booking->getState($bookingWorkflow);
With this Service declarations, (*7)
<service id="demo.booking_engine.state_workflow" class="Gmorel\StateWorkflowBundle\StateEngine\StateWorkflow" public="false">
<argument>Booking Workflow</argument>
<argument>demo.booking_engine.state_workflow</argument>
<tag name="gmorel.state_workflow_bundle.workflow" />
</service>
<service id="demo.booking_engine.state.incomplete" class="BookingEngine\Domain\State\Implementation\StateIncomplete" public="false">
<tag name="demo.booking_engine.state" />
</service>
<service id="demo.booking_engine.state.waiting_payment" class="BookingEngine\Domain\State\Implementation\StateWaitingPayment" public="false">
<tag name="demo.booking_engine.state" />
</service>
Implementation example
Booking Demo https://github.com/gmorel/StateWorkflowDemo, (*8)
Details
It will allow you to manage States
and especially their available Transitions
for an Entity
(for example a Booking class) implementing our interface HasStateInterface.
It is aiming at helping implementing a complex Workflow
where each State
implementing our interface StateInterface is responsible for its Transitions
(methods) to other States
.
Some Transitions
being impossible (not part of your Workflow
) and then throwing the exception UnsupportedStateTransitionException whenever called., (*9)
Each State
has a Symfony2 service tag:, (*10)
<service id="demo.booking_engine.state.paid" class="BookingEngine\Domain\State\Implementation\StatePaid" public="false">
<tag name="demo.booking_engine.state" />
</service>
This way you will be able to manage available States
for different Entities
by using other Symfony2 tags since Booking, Content and Customer entities shall not share the same Workflow
/States
).
You will then need to modify the Symfony2 CompilerPass in order to let your Workflow
be aware of its States
., (*11)
Adding new State:
In case you wish to add a new State
you will need to create a new Class implementing our interface StateInterface., (*12)
Adding new Transition:
In case you wish to add a new Transition
you will need to add a new method in your XXXStateInterface extending our StateInterface.
You can also use our helper AbstractState which would implement default behavior ie. a method throwing our UnsupportedStateTransitionException., (*13)
Installation
Step 1: Download the Bundle
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:, (*14)
$ composer require gmorel/state-workflow-bundle "~1.0"
This command requires you to have Composer installed globally, as explained
in the installation chapter
of the Composer documentation., (*15)
Step 2: Enable the Bundle
Then, enable the bundle by adding the following line in the app/AppKernel.php
file of your project:, (*16)
<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new Gmorel\StateWorkflowBundle\GmorelStateWorkflowBundle(),
);
// ...
}
// ...
}
How it works internally
, (*17)
Credits
Licence
MIT License (MIT), (*18)
Contributing
Feel free to enhance it and to share your ideas/enhancements., (*19)