Workflow(3)
๐ธ NAME ๐ธ
Workflow โ a simple, flexible system to implement workflows. bestie this is the Workflow(3) page from the User Contributed Perl Documentation, and yes it is that girl ๐
โจ VERSION โจ
This documentation describes version 2.09 of Workflow ๐
๐ SYNOPSIS ๐
okay so here's the whole vibe in code form. it's giving "everything you need to know in one giant snippet" and honestly? iconic. ๐
use Workflow::Factory qw( FACTORY );
# Defines a workflow of type 'myworkflow'
my $workflow_conf = 'workflow.yaml';
# contents of 'workflow.yaml'
type: myworkflow
time_zone: local # optional
description: |- # optional
This is my workflow.
history_class: My::Workflow::History # optional
state:
- name: INITIAL
action:
- name: "upload file"
resulting_state: uploaded
- name: uploaded
autorun: yes
action:
- name: "verify file"
resulting_state: "verified file"
condition:
- test: "$context->{user} ne 'CWINTERS'"
- name: "null"
resulting_state: annotated
condition:
- test: "$context->{user} eq 'CWINTERS'"
- name: "verified file"
action:
- name: annotate
condition:
- name: can_annotate
- name: "null"
condition:
- name: "!can_annotate"
- name: annotated
autorun: yes
may_stop: yes
action:
- name: "null"
resulting_state: finished
condition:
- name: completed
- name: completed
# end of workflow.yaml
# Defines actions available to the workflow
my $action_conf = 'action.yaml';
# contents of 'action.yaml'
action:
- name: "upload file"
class: MyApp::Action::Upload
field:
- name: path
label: "File Path"
is_required: yes
description: |-
Path to file
- name: "verify file"
class: MyApp::Action::Verify
validator:
- name: filesize_cap
value: "$file_size"
- name: annotate
class: MyApp::Action::Annotate
- name: "null"
class: Workflow::Action::Null
# end of action.yaml
# Defines conditions available to the workflow
my $condition_conf = 'condition.yaml';
# contents of 'condition.yaml'
condition:
- name: can_annotate
class: MyApp::Condition::CanAnnotate
# end of condition.yaml
# Defines validators available to the actions
my $validator_conf = 'validator.yaml';
# contents of 'validator.yaml'
validator:
- name: filesize_cap
class: MyApp::Validator::FileSizeCap
param:
- name: max_size
value: 20M
# end of 'validator.yaml'
# Stock the factory with the configurations; we can add more later if
# we want
$self->_factory()->add_config_from_file(
workflow => $workflow_conf,
action => $action_conf,
condition => $condition_conf,
validator => $validator_conf
);
# Instantiate a new workflow...
my $workflow = $self->_factory()->create_workflow( 'myworkflow' );
print "Workflow ", $workflow->id, " ",
"currently at state ", $workflow->state, "\n";
# Display available actions...
print "Available actions: ", $workflow->get_current_actions, "\n";
# Get the data needed for action 'upload file' (assumed to be
# available in the current state) and display the fieldname and
# description
print "Action 'upload file' requires the following fields:\n";
foreach my $field ( $workflow->get_action_fields( 'FOO' ) ) {
print $field->name, ": ", $field->description,
"(Required? ", $field->is_required, ")\n";
}
# Add data to the workflow context for the validators, conditions and
# actions to work with
my $context = $workflow->context;
$context->param( current_user => $user );
$context->param( sections => \@sections );
# Execute one of them
$workflow->execute_action( 'upload file',
{ path => $path_to_file });
print "New state: ", $workflow->state, "\n";
# Later.... fetch an existing workflow
my $id = get_workflow_id_from_user( ... );
my $workflow = $self->_factory()->fetch_workflow( 'myworkflow', $id );
print "Current state: ", $workflow->state, "\n";
๐ QUICK START ๐
The eg/ticket/ directory contains a configured workflow system. You can access the same data and logic in two ways โ well, the list says three things but the sentence says two, and honestly bestie I'm just the messenger ๐:
- a command-line application (
ticket.pl) - a CGI script (
ticket.cgi) - a web application (
ticket_web.pl)
To initialize:
perl ticket.pl --db
To run the command-line application:
perl ticket.pl
To access the database and data from CGI, add the relevant configuration for your web server and call ticket.cgi:
http://www.mysite.com/workflow/ticket.cgi
To start up the standalone web server:
perl ticket_web.pl
๐ซถ heads up: (Barring changes to HTTP::Daemon and forking the standalone server won't work on Win32; use CGI instead, although patches are always welcome.) so like... win32 besties, CGI is your moment โจ
For more info, see eg/ticket/README
๐ DESCRIPTION ๐
๐ฆ Overview ๐ฆ
This is a standalone workflow system. It is designed to fit into your system rather than force your system to fit to it. You can save workflow information to a database or the filesystem (or a custom storage). The different components of a workflow system can be included separately as libraries to allow for maximum reusibility.
(and yes, it really is spelled "reusibility" in the original โ no cap, I'm not touching it ๐)
๐ User Point of View ๐
As a user you only see two components, plus a third which is really embedded into another:
Workflow::Factoryโ The factory is your interface for creating new workflows and fetching existing ones. You also feed all the necessary configuration files and/or data structures to the factory to initialize it.Workflowโ When you get the workflow object from the workflow factory you can only use it in a few ways โ asking for the current state, actions available for the state, data required for a particular action, and most importantly, executing a particular action. Executing an action is how you change from one state to another.Workflow::Contextโ This is a blackboard for data from your application to the workflow system and back again. Each instantiation of aWorkflowhas its own context, and actions executed by the workflow can read data from and deposit data into the context.
๐ Developer Point of View ๐
The workflow system has four basic components:
- workflow โ The workflow is a collection of states; you define the states, how to move from one state to another, and under what conditions you can change states.
This is represented by the Workflow object. You normally do not need to subclass this object for customization. ๐
- action โ The action is defined by you or in a separate library. The action is triggered by moving from one state to another and has access to the workflow and more importantly its context.
The base class for actions is the Workflow::Action class. โจ
- condition โ Within the workflow you can attach one or more conditions to an action. These ensure that actions only get executed when certain conditions are met. Conditions are completely arbitrary: typically they will ensure the user has particular access rights, but you can also specify that an action can only be executed at certain times of the day, or from certain IP addresses, and so forth. Each condition is created once at startup then passed a context to check every time an action is checked to see if it can be executed.
The base class for conditions is the Workflow::Condition class. ๐
- validator โ An action can specify one or more validators to ensure that the data available to the action is correct. The data to check can be as simple or complicated as you like. Each validator is created once then passed a context and data to check every time an action is executed.
The base class for validators is the Workflow::Validator class. ๐
(stay tuned bestie, there's more of this page coming ๐ธ)
WORKFLOW BASICS ๐
Just a Bunch of States ๐ฆ
okay so bestie, a workflow is literally just a bunch of states with rules on how to move between them. these moves are called transitions, and they get triggered by some sort of event. a state is just a description of object properties. and no cap, you can describe a surprisingly large number of processes as a series of states and actions to move between them. the app shipped with this distribution uses a fairly common application to illustrate: the trouble ticket.
when you create a workflow you have one action available to you: create a new ticket ('create issue'). the workflow has a state 'INITIAL' when it is first created, but this is just a bootstrapping exercise since the workflow must always be in some state.
the workflow action 'create issue' has a property 'resulting_state', which just means: if you execute me properly the workflow will be in the new state 'CREATED'.
all this talk of 'states' and 'transitions' can be confusing, but just match them to what happens in real life โ you move from one action to another and at each step ask: what happens next?
you create a trouble ticket: what happens next? anyone can add comments to it and attach files to it while administrators can edit it and developers can start working on it. adding comments does not really change what the ticket is, it just adds information. attachments are the same, as is the admin editing the ticket.
but when someone starts work on the ticket, that is a different matter. when someone starts work they change the answer to: what happens next? whenever the answer to that question changes, that means the workflow has changed state. periodt.
Discover Information from the Workflow ๐
in addition to declaring what the resulting state will be from an action the action also has a number of 'field' properties that describe that data it required to properly execute it.
this is an example of discoverability. this workflow system is setup so you can ask it what you can do next as well as what is required to move on. so to use our ticket example we can do this, creating the workflow and asking it what actions we can execute right now:
my $wf = Workflow::$self->_factory()->create_workflow( 'Ticket' );
my @actions = $wf->get_current_actions;
we can also interrogate the workflow about what fields are necessary to execute a particular action:
print "To execute the action 'create issue' you must provide:\n\n";
my @fields = $wf->get_action_fields( 'create issue' );
foreach my $field ( @fields ) {
print $field->name, " (Required? ", $field->is_required, ")\n",
$field->description, "\n\n";
}
Provide Information to the Workflow ๐ฆ
to allow the workflow to run into multiple environments we must have a common way to move data between your application, the workflow and the code that moves it from one state to another.
whenever the Workflow::Factory creates a new workflow it associates the workflow with a Workflow::Context object. the context is what moves the data from your application to the workflow and the workflow actions.
for instance, the workflow has no idea what the 'current user' is. not only is it unaware from an application standpoint but it does not presume to know where to get this information. so you need to tell it, and you do so through the context.
the fact that the workflow system proscribes very little means it can be used in lots of different applications and interfaces. if a system is too closely tied to an interface (like the web) then you have to create some potentially ugly hacks to create a more convenient avenue for input to your system (such as an e-mail approving a document).
the Workflow::Context object is extremely simple to use โ you ask a workflow for its context and just get/set parameters on it:
# Get the username from the Apache object
my $username = $r->connection->user;
# ...set it in the context
$wf->context->param( user => $username );
# somewhere else you'll need the username:
$news_object->{created_by} = $wf->context->param( 'user' );
Controlling What Gets Executed ๐ฎ
a typical process for executing an action is:
- Get data from the user
- Fetch a workflow
- Set the data from the user to the workflow context
- Execute an action on the context
when you execute the action a number of checks occur. the action needs to ensure:
- The data presented to it are valid โ date formats, etc. This is done with a validator, more at
Workflow::Validator - The environment meets certain conditions โ user is an administrator, etc. This is done with a condition, more at
Workflow::Condition
once the action passes these checks and successfully executes we update the permanent workflow storage with the new state, as long as the application has declared it.
WORKFLOWS ARE OBSERVABLE ๐
Purpose ๐ฏ
it's useful to have your workflow generate events so that other parts of a system can see what's going on and react. for instance, say you have a new user creation process. you want to email the records of all users who have a first name of 'Sinead' because you're looking for your long-lost sister named 'Sinead'. you'd create an observer class like:
package FindSinead;
sub update {
my ( $class, $wf, $event, $event_args ) = @_;
return unless ( $event eq 'state change' );
return unless ( $event_args->{to} eq 'CREATED' );
my $context = $wf->context;
return unless ( $context->param( 'first_name' ) eq 'Sinead' );
my $user = $context->param( 'user' );
my $username = $user->username;
my $email = $user->email;
my $mailer = get_mailer( ... );
$mailer->send( 'foo@bar.com','Found her!',
"We found Sinead under '$username' at '$email' );
}
and then associate it with your workflow:
<workflow>
<type>SomeFlow</type>
<observer class="FindSinead" />
...
every time you create/fetch a workflow the associated observers are attached to it.
Events Generated ๐ก
you can attach listeners to workflows and catch events at a few points in the workflow lifecycle; these are the events fired:
createโ Issued after a workflow is first created.
No additional parameters.
fetchโ Issued after a workflow is fetched from the persister.
No additional parameters.
startupโ Issued at the beginning of the execute loop, before the first action is called.
No additional parameters.
finalizeโ Issued at the end of the execute loop, after all action are handled.
No additional parameters.
runโ Issued before a single action is executed. Will be followed by either a"save"or"rollback"event.
No additional parameters.
saveโ Issued after the workflow was saved after running a single action.
No additional parameters.
rollbackโ Issued after the execution of a single action failed.
No additional parameters.
completedโ Issued after a single action was successfully executed and saved.
Receives a hashref as second parameter holding the keys "state" and "action". $state includes the state of the workflow before the action was executed, $action is the action name that was executed.
state changeโ Issued after a single action is successfully executed, saved and results in a state change. The event will not be fired if you executed an action that did not result in a state change.
Receives a hashref as second parameter. The key "from" holds the name of the state before the action, "action" is the name of the action that was executed and "to" holding the name of the target (current) state.
add historyโ Issued after one or more history objects were added to a workflow object.
The additional argument is an arrayref of all Workflow::History objects added to the workflow. (Note that these will not be persisted until the workflow is persisted.)
Configuring โ๏ธ
you configure the observers directly in the 'workflow' configuration item. each 'observer' may have either a 'class' or 'sub' entry within it that defines the observer's location.
we load these classes at startup time. so if you specify an observer that doesn't exist you see the error when the workflow system is initialized rather than the system tries to use the observer.
for instance, the following defines two observers:
<workflow>
<type>ObservedItem</type>
<description>This is...</description>
<observer class="SomeObserver" />
<observer sub="SomeOtherObserver::Functions::other_sub" />
in the first declaration we specify the class ('SomeObserver') that will catch observations using its update() method. in the second we're naming exactly the subroutine (BAqother_sub()) in the class 'SomeOtherObserver::Functions' that will catch observations.
all configured observers get all events. it's up to each observer to figure out what it wants to handle. slay.
๐ธ WORKFLOW METHODS ๐ธ
ok so this next bit is all about the workflow object itself, not the whole big system around it. two different things bestie, keep up ๐
โจ Object Methods โจ
execute_action( $action_name, $args )โ runs the action called$action_name. usually this changes the state of the workflow, that's kind of its whole thing. but heads up: if$action_nameisn't in the current state, or it fails one of the conditions on the action, or it fails one of the validators on the action โ boom, exception. thrown. no mercy.
the $args you pass get checked against the validators to make sure the context stays in a valid state. once validation succeeds, the $args get merged into the context and the action executes as described above. ๐ซถ
after the action has successfully executed and the workflow has been saved, we issue an 'execute' observation, with the old state, the action name, and an autorun flag as extra parameters. so if you wanna write an observer you could make a method with this signature:
sub update {
my ( $class, $workflow, $action, $old_state, $action_name )
= @_;
if ( $action eq 'execute' ) { .... }
}
we also issue a 'change state' observation if the executed action resulted in a new state. see "WORKFLOWS ARE OBSERVABLE" above for how we use and register observers. ๐
Returns: new state of workflow
get_current_actions( $group )โ gives you a list of action names that are available from the current state for the given environment. so like, if you keep yourcontext()the same and then callexecute_action()with one of these action names, you shouldn't trigger any condition error, because the action has already been screened for conditions. slay.
if you wanna divide actions into groups (say, a state change group and an approval group, which need to show up in different places on the page), just add a group property to your action:
<action name="terminate request" group="state change" class="MyApp::Action::Terminate" />
<action name="approve request" group="approval" class="MyApp::Action::Approve" />
my @actions = $wf->get_current_actions("approval");
$group should be a string that represents the desired group name. in @actions you'll get the list of action names available from the current state for the given environment, limited by group. $group is an optional parameter btw. ๐
Returns: list of strings representing available actions
get_all_actionsโ gives you a list of ALL the action names defined for the current state, whether or not they're actually available from the current environment.
Returns: list of strings representing available actions
get_action( $action_name )โ fetches the action object associated with$action_namein the current workflow state. it will throw an exception if:- No workflow state exists with a name of the current state. (this is usually some kind of configuration error and should be caught at initialization time, so it should not happen. famous last words.)
- No action
$action_nameexists in the current state. - No action
$action_nameexists in the workflow universe. - One of the conditions for the action in this state is not met.
get_action_fields( $action_name )โ returns a list ofWorkflow::InputFieldobjects for the given$action_name. if$action_nameisn't in the current state, or isn't accessible by the environment, an exception gets thrown. ๐ญ
Returns: list of Workflow::InputField objects
add_history( @( \%params | $wf_history_object ) )โ adds any number of histories to the workflow. usually this is done by an action insideexecute_action()or by one of the observers of that action. and no, this history will not be saved untilexecute_action()is complete. patience bestie ๐
you can add a list of either hashrefs with history info in them, or full Workflow::History objects. trying to add anything else results in an exception, and none of the items get added. all or nothing, periodt.
successfully adding the history objects results in an 'add history' observation being thrown. see "WORKFLOWS ARE OBSERVABLE" above for more.
Returns: nothing
get_history()โ returns a list of history objects for this workflow. note that some of them may be unsaved if you call this during theexecute_action()process. ๐งget_unsaved_history()โ returns a list of all unsaved history objects for this workflow.clear_history()โ clears all the transient history objects from the workflow object. not from long-term storage, just from here. โจset( $property, $value )โ this method exists to overwriteClass::Accessorso only certain callers can set properties. the caller has to be in aWorkflownamespace package. sets the property to the value, or throws aWorkflow::Exception. ๐ฅ
๐ Observer methods ๐
add_observer( @observers )โ adds one or more observers to a"Workflow"instance. an observer is a function. see"notify_observers"for its calling convention.
this function is used internally by "Workflow::Factory" to implement observability as documented in the section "WORKFLOWS ARE OBSERVABLE".
notify_observers( @arguments )โ calls all the observer functions registered through"add_observer", with the workflow as the first argument and@argumentsas the remaining arguments:
$observer->( $wf, @arguments );
used by various parts of the library to notify observers of workflow-instance-related events. ๐
๐ Properties ๐
unless otherwise noted, properties are read-only. iykyk.
Configuration Properties
some properties get set in the configuration file for each workflow. these stay static once the workflow is instantiated. no take-backs.
typeโ what type of workflow this is. you may have many individual workflows associated with a type, or you may have many different types running in a single workflow engine.descriptionโ description (usually brief, hopefully with a URL...) of this workflow.time_zoneโ Workflow uses theDateTimemodule to create all its date objects. thetime_zoneparameter lets you pass a time zone value directly to theDateTimenewmethod for all cases where Workflow needs to create a date object. see theDateTimemodule for acceptable values. ๐
Dynamic Properties
you can get these properties from any workflow object. bestie privileges ๐ซถ
idโ the ID of this workflow. this will always be defined, since when theWorkflow::Factorycreates a new workflow it first saves it to long-term storage.stateโ the current state of the workflow.last_update(read-write) โ date of the workflow's last update.last_action_executed(read) โ contains the name of the action that was tried to be executed last, even if the execution couldn't be completed, like due to failed parameter validation or an exception on code execution. super useful for finding the step that failed when you're using autorun sequences, because"state"will just return the state from which it was called. ๐context(read-write, see below) โ aWorkflow::Contextobject associated with this workflow. this should never be undefined, since theWorkflow::Factorysets an empty context into the workflow when it's instantiated.
if you add a context to a workflow and one already exists, the values from the new workflow will overwrite values in the existing workflow. this is a shallow merge, so with the following:
$wf->context->param( drinks => [ 'coke', 'pepsi' ] );
my $context = Workflow::Context->new();
$context->param( drinks => [ 'beer', 'wine' ] );
$wf->context( $context );
print 'Current drinks: ', join( ', ', @{ $wf->context->param( 'drinks' ) } );
you will see:
Current drinks: beer, wine
๐ Internal Methods ๐
init( $id, $current_state, \%workflow_config, \@wf_states )โ THIS SHOULD ONLY BE CALLED BY THEWorkflow::Factory. do not call this, and do not call thenew()method yourself either โ you will only get an exception. your one and only interface for creating and fetching workflows is through the factory. that's the rule ๐
this is called by the inherited constructor, and it sets the $current_state value to the property "state", and uses the other non-state values from "\%config" to set parameters via the inherited param().
_get_workflow_state( [ $state ] )โ returns theWorkflow::Stateobject corresponding to$state, which defaults to the current state._set_workflow_state( $wf_state )โ assigns theWorkflow::Stateobject$wf_stateto the workflow._get_next_state( $action_name )โ returns the name of the next state given the action$action_name. throws an exception if$action_nameisn't contained in the current state.
๐ธ Initial workflow history ๐ธ
when you're creating an initial Workflow::History record while creating a workflow, several fields are required.
get_initial_history_dataโ this method returns a list of key/value pairs to add in the initial history record. the following defaults get returned:"user"- value:
"n/a"
- value:
"description"- value:
"Create new workflow"
- value:
"action"- value:
"Create workflow"
- value:
override this method to change the values from their defaults. e.g. โจ
sub get_initial_history_data {
return (
user => 1,
description => "none",
action => "run"
);
}
๐ CONFIGURATION AND ENVIRONMENT ๐
the configuration of Workflow is done using the format of your choice. currently XML and Perl are implemented, but additional formats can be added. please refer to Workflow::Config for implementation details. ๐
Configuration examples
XML configuration
<workflow>
<type>myworkflow</type>
<class>My::Workflow</class> <!-- optional -->
<initial_state>INITIAL</initial_state> <!-- optional -->
<time_zone>local</time_zone> <!-- optional -->
<description>This is my workflow.</description> <!-- optional -->
<!-- List one or more states -->
<state name="INITIAL">
<action name="upload file" resulting_state="uploaded" />
<action name="cancel upload" resulting_state="finished" />
</state>
<state name="uploaded">
<action name="verify file">
<resulting_state return="redo" state="INITIAL" />
<resulting_state return="finished" state="finished"/>
</action>
</state>
<state name="finished" />
</workflow>
๐ฆ Logging ๐ฆ
as of version 2.0, Workflow lets application developers pick their own logging solution of preference: the library is a Log::Any log producer. see Log::Any::Adapter for examples on how to configure logging. and for those of you who wanna keep running your Log::Log4perl configuration, please install Log::Any::Adapter::Log4perl and add one "use" statement and one line after the initialization of "Log::Log4perl":
use Log::Log4perl;
use Log::Any::Adapter; # Add this additional use-statement
Log::Log4perl::init('/etc/log4perl.conf');
Log::Any::Adapter->set( 'Log4perl' ); # Additional: Log::Any initialization
๐ DEPENDENCIES ๐
Okay so bestie, the full list of dependencies lives in the cpanfile inside the distribution archive. That's the master list, the blueprint, the whole vibe. โจ
On top of that, there are additional dependencies listed by feature, and right now this distribution supports exactly one of those features:
"examples"โ the extra deps you need to actually run the example applications. That's it, that's the list. ๐
๐ญ BUGS AND LIMITATIONS ๐ญ
The known bugs and limitations are chilling over in the Github issue tracker:
<https://github.com/perl-workflow/perl-workflow/issues>
No cap, that's where they live. ๐
๐ซถ BUG REPORTING ๐ซถ
Found something broken? Bug reporting should be done either via Github issues:
<https://github.com/perl-workflow/perl-workflow/issues>
And yes, a list of currently known issues can be seen via that same URL. One link, two jobs, we love an efficient icon. ๐
๐ธ TEST ๐ธ
Wanna run the test suite? You do it using prove:
% prove --lib
Some of the tests are reserved for the developers and they only run if the environment variable TEST_AUTHOR is set to true. And the requirements for those special tests will only be installed through Dist::Zilla's "authordeps" command:
% dzil authordeps --missing | cpanm --notest
Also! The test that verifies the (http/https) links in the POD documentation only runs when the variable POD_LINKS is set. So if the links test ghosted you, that's why. ๐
โจ CODING STYLE โจ
Currently the code is formatted using Perl::Tidy. The resource file can be downloaded from the central repository:
notes/perltidyrc
Consistency is the aesthetic, periodt. ๐
๐ PROJECT ๐
The Workflow project is currently hosted on GitHub:
GitHub: <https://github.com/perl-workflow/perl-workflow>
๐ฆ REPOSITORY ๐ฆ
The code is kept under revision control using Git:
<https://github.com/perl-workflow/perl-workflow/tree/master/>
๐ OTHER RESOURCES ๐
- MetaCPAN
<https://metacpan.org/release/Workflow>
๐ COPYRIGHT ๐
Copyright (c) 2003 Chris Winters and Arvato Direct; Copyright (c) 2004-2021 Chris Winters. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
๐ AUTHORS ๐
Jonas B. (jonasbn) <jonasbn@cpan.org>, current maintainer.
Chris Winters <chris@cwinters.com>, original author.
The following folks have also helped out (listed here in no particular order):
Thanks for to Michiel W. Beijen for fix to badly formatted URL, included in release 1.52
Several PRs (13 to be exact) from Erik Huelsmann resulting in release 1.49. Yet another batch of PRs resulted in release 1.50
PR from Mohammad S Anwar correcting some POD errors, included in release 1.49
Bug report from Petr Pisar resulted in release 1.48
Bug report from Tina Mรผller (tinita) resulted in release 1.47
Bug report from Slaven Reziฤ resulting in maintenance release 1.45
Feature and bug fix by dtikhonov resulting in 1.40 (first pull request on Github)
Sรฉrgio Alves, patch to timezone handling for workflow history deserialized using DBI persister resulting in 1.38
Heiko Schlittermann for context serialization patch resulting in 1.36
Scott Harding, for lazy evaluation of conditions and for nested conditions, see Changes file: 1.35
Oliver Welter, patch implementing custom workflows, see Changes file: 1.35 and patch related to this in 1.37 and factory subclassing also in 1.35. Improvements in logging for condition validation in 1.43 and 1.44 and again a patch resulting in release 1.46
Steven van der Vegt, patch for autorun in initial state and improved exception handling for validators, see Changes file: 1.34_1
Andrew O'Brien, patch implementing dynamic reloaded of flows, see Changes file: 1.33
Sergei Vyshenski, bug reports - addressed and included in 1.33, Sergei also maintains the FreeBSD port
Alejandro Imass, improvements and clarifications, see Changes file: 1.33
Danny Sadinoff, patches to give better control of initial state and history records for workflow, see Changes file: 1.33
Thomas Erskine, for patch adding new accessors and fixing several bugs see Changes file 1.33
Ivan Paponov, for patch implementing action groups, see Changes file, 1.33
Robert Stockdale, for patch implementing dynamic names for conditions, see Changes file, 1.32
Jim Brandt, for patch to Workflow::Config::XML. See Changes file, 0.27 and 0.30
Alexander Klink, for: patches resulting in 0.23, 0.24, 0.25, 0.26 and 0.27
Michael Bell, for patch resulting in 0.22
Martin Bartosch, for bug reporting and giving the solution not even using a patch (0.19 to 0.20) and a patch resulting in 0.21
Randal Schwartz, for testing 0.18 and swiftly giving feedback (0.18 to 0.19)
Chris Brown, for a patch to Workflow::Config::Perl (0.17 to 0.18)
Dietmar Hanisch <Dietmar.Hanisch@Bertelsmann.de> - Provided most of the good ideas for the module and an excellent example of everyday use.
Tom Moertel <tmoertel@cpan.org> gave me the idea for being able to attach event listeners (observers) to the process.
Michael Roberts <michael@vivtek.com> graciously released the 'Workflow' namespace on CPAN.
Michael Schwern <schwern@pobox.org> barked via RT about a dependency problem and CPAN naming issue.
Jim Smith <jgsmith@tamu.edu> - Contributed patches (being able to subclass Workflow::Factory) and good ideas.
Martin Winkler <mw@arsnavigandi.de> - Pointed out a bug and a few other items.
and that's the whole page bestie!! ๐โจ๐
This one's shipping as perl v5.42.0, dated 2026-01-17, and you just finished reading Workflow(3). Slay, go build something iconic with it. ๐ฆ๐๐
๐ฆ where this page comes from
- fedora 44: perl-Workflow 2.09-2.fc44 ยท GPL-1.0-or-later OR Artistic-1.0-Perl
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms