Download the PHP package wol-soft/php-workflow without Composer
On this page you can find all versions of the php package wol-soft/php-workflow. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download wol-soft/php-workflow
More information about wol-soft/php-workflow
Files in wol-soft/php-workflow
Package php-workflow
Short Description Stick together workflows
License MIT
Homepage https://github.com/wol-soft/php-workflow
Informations about the package php-workflow
php-workflow
Create controlled workflows from small pieces.
This library provides a predefined set of stages to glue together your workflows. You implement small self-contained pieces of code and define the execution order - everything else will be done by the execution control.
Bonus: you will get an execution log for each executed workflow - if you want to see what's happening.
Table of Contents
- Workflow vs. process
- Installation
- Example workflow
- Workflow container
- Stages
- Workflow control
- Nested workflows
- Loops
- Step dependencies
- Required container values
- Error handling, logging and debugging
- Custom output formatter
- Tests
- Workshop
Workflow vs. process
Before we start to look at coding with the library let's have a look, what a workflow implemented with this library can and what a workflow can't.
Let's assume we want to sell an item via an online shop. If a customer purchases an item he walks through the process of purchasing an item. This process contains multiple steps. Each process step can be represented by a workflow implemented with this library. For example:
- Customer registration
- Add items to the basket
- Checkout the basket
- ...
This library helps you to implement the process steps in a structured way. It doesn't control the process flow.
Now we know which use cases this library aims at. Now let's install the library and start coding.
Installation
The recommended way to install php-workflow is through Composer:
Requirements of the library:
- Requires at least PHP 7.4
Example workflow
Let's have a look at a code example first. Our example will represent the code to add a song to a playlist in a media player. Casually you will have a controller method which glues together all necessary steps with many if's, returns, try-catch blocks and so on. Now let's have a look at a possible workflow definition:
This workflow may create an execution log which looks like the following (more examples coming up later):
Now let's check what exactly happens. Each step of your workflow is represented by an own class which implements the step. Steps may be used in multiple workflows (for example the CurrentUserIsAllowedToEditPlaylistValidator can be used in every workflow which modifies playlists). Each of these classes representing a single step must implement the \PHPWorkflow\Step\WorkflowStep interface. Until you call the executeWorkflow method no step will be executed.
By calling the executeWorkflow method the workflow engine is triggered to start the execution with the first used stage. In our example the validations will be executed first. If all validations are successfully the next stage will be executed otherwise the workflow execution will be cancelled.
Let's have a more precise look at the implementation of a single step through the example of the before step AcceptOpenSuggestionForSong. Some feature background to understand what's happening in our example: our application allows users to suggest songs for playlists. If the owner of a playlist adds a song to a playlist which already exists as an open suggestion the suggestion shall be accepted instead of adding the song to the playlist and leave the suggestion untouched. Now let's face the implementation with some inline comments to describe the workflow control:
Workflow container
Now let's have a more detailed look at the WorkflowContainer which helps us, to share data and objects between our workflow steps. The relevant objects for our example workflow is the User who wants to add the song, the Song object of the song to add and the Playlist object. Before we execute our workflow we can set up a WorkflowContainer which contains all relevant objects:
The workflow container provides the following interface:
Each workflow step may define requirements, which entries must be present in the workflow container before the step is executed. For more details have a look at Required container values.
Alternatively to set and get the values from the WorkflowContainer via string keys you can extend the WorkflowContainer and add typed properties/functions to handle values in a type-safe manner:
When we execute the workflow via executeWorkflow we can inject the WorkflowContainer.
Another possibility would be to define a step in the Prepare stage (e.g. PopulateAddSongToPlaylistContainer) which populates the automatically injected empty WorkflowContainer object.
Stages
The following predefined stages are available when defining a workflow:
- Prepare
- Validate
- Before
- Process
- OnSuccess
- OnError
- After
Each stage has a defined set of stages which may be called afterwards (e.g. you may skip the Before stage). When setting up a workflow your IDE will support you by suggesting only possible next steps via autocompletion. Each workflow must contain at least one step in the Process stage.
Any step added to the workflow may throw an exception. Each exception will be caught and is handled like a failed step. If a step in the stages Prepare, Validate (see details for the stage) or Before fails, the workflow is failed and will not be executed further.
Any step may skip or fail the workflow via the WorkflowControl. If the Process stage has been executed and any later step tries to fail or skip the whole workflow it's handled as a failed/skipped step.
Now let's have a look at some stage-specific details.
Prepare
This stage allows you to add steps which must be executed before any validation or process execution is triggered. Steps may contain data loading, gaining workflow relevant semaphores, etc.
Validate
This stage allows you to execute validations. There are two types of validations: hard validations and soft validations. All hard validations of the workflow will be executed before the soft validations. If a hard validation fails the workflow will be stopped immediately (e.g. access right violations). All soft validations of the workflow will be executed independently of their result. All failing soft validations will be collected in a \PHPWorkflow\Exception\WorkflowValidationException which is thrown at the end of the validation stage if any of the soft validations failed.
When you attach a validation to your workflow the second parameter of the validate method defines if the validation is a soft or a hard validation:
In the provided example any of the soft validators may fail (e.g. the SongGenreMatchesPlaylistGenreValidator checks if the genre of the song matches the playlist, the PlaylistContainsNoSongsFromInterpret may check for duplicated interprets). The thrown WorkflowValidationException allows us to determine all violations and set up a corresponding error message. If all validators pass the next stage will be executed.
Before
This stage allows you to perform preparatory steps with the knowledge that the workflow execution is valid. This steps may contain the allocation of resources, filtering the data to process etc.
Process
This stage contains your main logic of the workflow. If any of the steps fails no further steps of the process stage will be executed.
OnSuccess
This stage allows you to define steps which shall be executed if all steps of the Process stage are executed successfully. For example logging, notifications, sending emails, etc.
All steps of the stage will be executed, even if some steps fail. All failing steps will be reported as warnings.
OnError
This stage allows you to define steps which shall be executed if any step of the Process stage fails. For example logging, setting up recovery data, etc.
All steps of the stage will be executed, even if some steps fail. All failing steps will be reported as warnings.
After
This stage allows you to perform cleanup steps after all other stages have been executed. The steps will be executed regardless of the successful execution of the Process stage.
All steps of the stage will be executed, even if some steps fail. All failing steps will be reported as warnings.
Workflow control
The WorkflowControl object which is injected into each step provides the following interface to interact with the workflow:
Nested workflows
If some of your steps become more complex you may want to have a look into the NestedWorkflow
wrapper which allows you to use a second workflow as a step of your workflow:
Each nested workflow must be executable (contain at least one Process step).
The debug log of your nested workflow will be embedded in the debug log of your main workflow.
As you can see in the example you can inject a dedicated WorkflowContainer into the nested workflow.
The nested workflow will gain access to a merged WorkflowContainer which provides all data and methods of your main workflow container and your nested container.
If you add additional data to the merged container the data will be present in your main workflow container after the nested workflow execution has been completed.
For example your implementations of the steps used in the nested workflow will have access to the keys nested-data
and parent-data
.
Loops
If you handle multiple entities in your workflows at once you may need loops.
An approach would be to set up a single step which contains the loop and all logic which is required to be executed in a loop.
But if there are multiple steps required to be executed in the loop you may want to split the step into various steps.
By using the Loop
class you can execute multiple steps in a loop.
For example let's assume our AddSongToPlaylist
becomes a AddSongsToPlaylist
workflow which can add multiple songs at once:
Our process step now implements a loop controlled by the SongLoop
class.
The loop contains our two steps AddSongToPlaylist
and ClearSongCache
.
The implementation of the SongLoop
class must implement the PHPWorkflow\Step\LoopControl
interface.
Let's have a look at an example implementation:
A loop step may contain a nested workflow if you need more complex steps.
To control the flow of the loop from the steps you can use the continue
and break
methods on the WorkflowControl
object.
By default, a loop is stopped if a step fails.
You can set the second parameter of the Loop
class ($continueOnError
) to true to continue the execution with the next iteration.
If you enable this option a failed step will not result in a failed workflow.
Instead, a warning will be added to the process log.
Calls to failWorkflow
and skipWorkflow
will always cancel the loop (and consequently the workflow) independent of the option.
Step dependencies
Each step implementation may apply dependencies to the step. By defining dependencies you can set up validation rules which are checked before your step is executed (for example: which data must be provided in the workflow container). If any of the dependencies is not fulfilled, the step will not be executed and is handled as a failed step.
Note: as this feature uses Attributes, it is only available if you use PHP >= 8.0.
Required container values
With the \PHPWorkflow\Step\Dependency\Required
attribute you can define keys which must be present in the provided workflow container.
The keys consequently must be provided in the initial workflow or be populated by a previous step.
Additionally to the key you can also provide the type of the value (eg. string
).
To define the dependency you simply annotate the provided workflow container parameter:
The following types are supported: string
, bool
, int
, float
, object
, array
, iterable
, scalar
as well as object type hints by providing the corresponding FQCN.
Error handling, logging and debugging
The executeWorkflow method returns an WorkflowResult object which provides the following methods to determine the result of the workflow:
As stated above workflows with failing steps before the Process stage will be aborted, otherwise the Process stage and all downstream stages will be executed.
By default, the execution of a workflow throws an exception if an error occurs. The thrown exception will be a \PHPWorkflow\Exception\WorkflowException which allows you to access the WorkflowResult object via the getWorkflowResult method.
The debug method provides an execution log including all processed steps with their status, attached data as well as a list of all warnings and performance data.
Some example outputs for our example workflow may look like the following.
Successful execution
Note the additional data added to the debug log for the Process stage and the NotifySubscribers step via the attachStepInfo method of the WorkflowControl.
Failed workflow
In this example the CurrentUserIsAllowedToEditPlaylistValidator step threw an exception with the message playlist locked
.
Workflow skipped
In this example the AcceptOpenSuggestionForSong step found a matching open suggestion and successfully accepted the suggestion. Consequently, the further workflow execution is skipped.
Custom output formatter
The output of the debug
method can be controlled via an implementation of the OutputFormat
interface.
By default a string representation of the execution will be returned (just like the example outputs).
Currently the following additional formatters are implemented:
Formatter | Description |
---|---|
StringLog |
The default formatter. Creates a string representation. Example: $result->debug(); |
WorkflowGraph |
Creates a SVG file containing a graph which represents the workflow execution. The generated image will be stored in the provided target directory. Requires dot executable.Example: $result->debug(new WorkflowGraph('/var/log/workflow/graph')); |
GraphViz |
Returns a string containing GraphViz code for a graph representing the workflow execution. Example: $result->debug(new GraphViz()); |
Tests
The library is tested via PHPUnit.
After installing the dependencies of the library via composer update
you can execute the tests with ./vendor/bin/phpunit
(Linux) or vendor\bin\phpunit.bat
(Windows).
The test names are optimized for the usage of the --testdox
output.
If you want to test workflows you may include the PHPWorkflow\Tests\WorkflowTestTrait
which adds methods to simplify asserting workflow results.
The following methods are added to your test classes:
Workshop
Maybe you want to try out the library and lack a simple idea to solve with the library. Therefore, here's a small workshop which covers most of the library's features. Implement the task given below (which, to be fair, can surely be implemented easier without the library but the library is designed to support large workflows with a lot of business logic) to have an idea how coding with the library works.
Your data input for this task is a simple array with a list of persons in the following format:
The workflow shall implement the following steps:
- Check if the list is empty. In this case finish the workflow directly
- Check if the list contains persons with an age below 18 years. In this case the workflow should fail
- Make sure each firstname and lastname is populated. If any empty fields are detected, the workflow should fail
- Before processing the list normalize the firstnames and lastnames (
ucfirst
andtrim
)- Make sure the workflow log contains the amount of changed data sets
- Process the list. The processing itself splits up into the following steps:
- Make sure a directory of your choice contains a CSV file for each age from the input data
- If the file doesn't exist, create a new file
- The workflow log must contain information about new files
- Add all persons from your input data to the corresponding files
- The workflow log must display the amount of persons added to each file
- Make sure a directory of your choice contains a CSV file for each age from the input data
- If all persons were persisted successfully create a ZIP backup of all files
- If an error occurred rollback to the last existing ZIP backup
If you have finished implementing the workflow, pick a step of your choice and implement a unit test for the step.