{"id":15017331,"url":"https://github.com/perl-workflow/perl-workflow","last_synced_at":"2026-03-09T18:37:14.213Z","repository":{"id":13768941,"uuid":"16463822","full_name":"perl-workflow/perl-workflow","owner":"perl-workflow","description":"Workflow - simple, flexible system to implement workflows/state machines","archived":false,"fork":false,"pushed_at":"2026-03-02T17:32:43.000Z","size":1927,"stargazers_count":34,"open_issues_count":10,"forks_count":11,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-03-02T20:57:25.721Z","etag":null,"topics":["hacktoberfest","perl","perl-module","perl5","state-machine","workflow"],"latest_commit_sha":null,"homepage":"http://workflow.pm/perl-workflow/","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/perl-workflow.png","metadata":{"files":{"readme":"README.md","changelog":"Changes.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2014-02-02T21:33:10.000Z","updated_at":"2026-03-02T17:32:54.000Z","dependencies_parsed_at":"2023-12-18T16:52:45.675Z","dependency_job_id":"be06a255-cdc5-4e45-9057-7ae54cc1a75a","html_url":"https://github.com/perl-workflow/perl-workflow","commit_stats":null,"previous_names":["perl-workflow/perl-workflow","jonasbn/perl-workflow"],"tags_count":85,"template":false,"template_full_name":null,"purl":"pkg:github/perl-workflow/perl-workflow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perl-workflow%2Fperl-workflow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perl-workflow%2Fperl-workflow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perl-workflow%2Fperl-workflow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perl-workflow%2Fperl-workflow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/perl-workflow","download_url":"https://codeload.github.com/perl-workflow/perl-workflow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perl-workflow%2Fperl-workflow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30307548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T17:35:44.120Z","status":"ssl_error","status_checked_at":"2026-03-09T17:35:43.707Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","perl","perl-module","perl5","state-machine","workflow"],"created_at":"2024-09-24T19:50:18.966Z","updated_at":"2026-03-09T18:37:14.196Z","avatar_url":"https://github.com/perl-workflow.png","language":"Perl","funding_links":[],"categories":["Building"],"sub_categories":["Workflows"],"readme":"[![CPAN version](https://badge.fury.io/pl/Workflow.svg)](http://badge.fury.io/pl/Workflow)\n[![Build status](https://github.com/perl-workflow/perl-workflow/actions/workflows/ci.yml/badge.svg)](https://github.com/perl-workflow/perl-workflow/actions/workflows/ci.yml)\n[![Coverage Status](https://coveralls.io/repos/github/perl-workflow/perl-workflow/badge.svg?branch=master)](https://coveralls.io/github/perl-workflow/perl-workflow?branch=master)\n\n# NAME\n\nWorkflow - Simple, flexible system to implement workflows\n\n# VERSION\n\nThis documentation describes version 2.09 of Workflow\n\n# SYNOPSIS\n\n    use Workflow::Factory qw( FACTORY );\n\n    # Defines a workflow of type 'myworkflow'\n    my $workflow_conf  = 'workflow.yaml';\n\n    # contents of 'workflow.yaml'\n    type: myworkflow\n    time_zone: local                                       # optional\n    description: |-                                        # optional\n       This is my workflow.\n    history_class: My::Workflow::History                   # optional\n    state:\n    - name: INITIAL\n      action:\n      - name: \"upload file\"\n        resulting_state: uploaded\n    - name: uploaded\n      autorun: yes\n      action:\n      - name: \"verify file\"\n        resulting_state: \"verified file\"\n        condition:\n        - test: \"$context-\u003e{user} ne 'CWINTERS'\"\n      - name: \"null\"\n        resulting_state: annotated\n        condition:\n        - test: \"$context-\u003e{user} eq 'CWINTERS'\"\n    - name: \"verified file\"\n      action:\n      - name: annotate\n        condition:\n        - name: can_annotate\n      - name: \"null\"\n        condition:\n        - name: \"!can_annotate\"\n    - name: annotated\n      autorun: yes\n      may_stop: yes\n      action:\n      - name: \"null\"\n        resulting_state: finished\n        condition:\n        - name: completed\n    - name: completed\n\n    # end of workflow.yaml\n\n\n    # Defines actions available to the workflow\n    my $action_conf    = 'action.yaml';\n\n    # contents of 'action.yaml'\n\n    action:\n    - name: \"upload file\"\n      class: MyApp::Action::Upload\n      field:\n      - name: path\n        label: \"File Path\"\n        is_required: yes\n        description: |-\n          Path to file\n    - name: \"verify file\"\n      class: MyApp::Action::Verify\n      validator:\n      - name: filesize_cap\n        value: \"$file_size\"\n    - name: annotate\n      class: MyApp::Action::Annotate\n    - name: \"null\"\n      class: Workflow::Action::Null\n\n    # end of action.yaml\n\n\n    # Defines conditions available to the workflow\n    my $condition_conf = 'condition.yaml';\n\n    # contents of 'condition.yaml'\n\n    condition:\n    - name: can_annotate\n      class: MyApp::Condition::CanAnnotate\n\n    # end of condition.yaml\n\n\n    # Defines validators available to the actions\n    my $validator_conf = 'validator.yaml';\n\n    # contents of 'validator.yaml'\n\n    validator:\n    - name: filesize_cap\n      class: MyApp::Validator::FileSizeCap\n      param:\n      - name: max_size\n        value: 20M\n\n    # end of 'validator.yaml'\n\n\n    # Stock the factory with the configurations; we can add more later if\n    # we want\n    $self-\u003e_factory()-\u003eadd_config_from_file(\n        workflow   =\u003e $workflow_conf,\n        action     =\u003e $action_conf,\n        condition  =\u003e $condition_conf,\n        validator  =\u003e $validator_conf\n    );\n\n    # Instantiate a new workflow...\n    my $workflow = $self-\u003e_factory()-\u003ecreate_workflow( 'myworkflow' );\n    print \"Workflow \", $workflow-\u003eid, \" \",\n          \"currently at state \", $workflow-\u003estate, \"\\n\";\n\n    # Display available actions...\n    print \"Available actions: \", $workflow-\u003eget_current_actions, \"\\n\";\n\n    # Get the data needed for action 'upload file' (assumed to be\n    # available in the current state) and display the fieldname and\n    # description\n\n    print \"Action 'upload file' requires the following fields:\\n\";\n    foreach my $field ( $workflow-\u003eget_action_fields( 'FOO' ) ) {\n        print $field-\u003ename, \": \", $field-\u003edescription,\n              \"(Required? \", $field-\u003eis_required, \")\\n\";\n    }\n\n    # Add data to the workflow context for the validators, conditions and\n    # actions to work with\n\n    my $context = $workflow-\u003econtext;\n    $context-\u003eparam( current_user =\u003e $user );\n    $context-\u003eparam( sections =\u003e \\@sections );\n\n    # Execute one of them\n    $workflow-\u003eexecute_action( 'upload file',\n                               { path =\u003e $path_to_file });\n\n    print \"New state: \", $workflow-\u003estate, \"\\n\";\n\n    # Later.... fetch an existing workflow\n    my $id = get_workflow_id_from_user( ... );\n    my $workflow = $self-\u003e_factory()-\u003efetch_workflow( 'myworkflow', $id );\n    print \"Current state: \", $workflow-\u003estate, \"\\n\";\n\n# QUICK START\n\nThe `eg/ticket/` directory contains a configured workflow system.\nYou can access the same data and logic in two ways:\n\n- a command-line application (ticket.pl)\n- a CGI script               (ticket.cgi)\n- a web application          (ticket\\_web.pl)\n\nTo initialize:\n\n        perl ticket.pl --db\n\nTo run the command-line application:\n\n        perl ticket.pl\n\nTo access the database and data from CGI, add the relevant\nconfiguration for your web server and call ticket.cgi:\n\n        http://www.mysite.com/workflow/ticket.cgi\n\nTo start up the standalone web server:\n\n        perl ticket_web.pl\n\n(Barring changes to HTTP::Daemon and forking the standalone server\nwon't work on Win32; use CGI instead, although patches are always\nwelcome.)\n\nFor more info, see `eg/ticket/README`\n\n# DESCRIPTION\n\n## Overview\n\nThis is a standalone workflow system. It is designed to fit into your\nsystem rather than force your system to fit to it. You can save\nworkflow information to a database or the filesystem (or a custom\nstorage). The different components of a workflow system can be\nincluded separately as libraries to allow for maximum reusibility.\n\n## User Point of View\n\nAs a user you only see two components, plus a third which is really\nembedded into another:\n\n- [Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory) - The factory is your interface for creating new\nworkflows and fetching existing ones. You also feed all the necessary\nconfiguration files and/or data structures to the factory to\ninitialize it.\n- [Workflow](https://metacpan.org/pod/Workflow) - When you get the workflow object from the workflow\nfactory you can only use it in a few ways -- asking for the current\nstate, actions available for the state, data required for a particular\naction, and most importantly, executing a particular action. Executing\nan action is how you change from one state to another.\n- [Workflow::Context](https://metacpan.org/pod/Workflow%3A%3AContext) - This is a blackboard for data from your\napplication to the workflow system and back again. Each instantiation\nof a [Workflow](https://metacpan.org/pod/Workflow) has its own context, and actions executed by the\nworkflow can read data from and deposit data into the context.\n\n## Developer Point of View\n\nThe workflow system has four basic components:\n\n- **workflow** - The workflow is a collection of states; you define the\nstates, how to move from one state to another, and under what\nconditions you can change states.\n\n    This is represented by the [Workflow](https://metacpan.org/pod/Workflow) object. You normally do not\n    need to subclass this object for customization.\n\n- **action** - The action is defined by you or in a separate library. The\naction is triggered by moving from one state to another and has access\nto the workflow and more importantly its context.\n\n    The base class for actions is the [Workflow::Action](https://metacpan.org/pod/Workflow%3A%3AAction) class.\n\n- **condition** - Within the workflow you can attach one or more\nconditions to an action. These ensure that actions only get executed\nwhen certain conditions are met. Conditions are completely arbitrary:\ntypically they will ensure the user has particular access rights, but\nyou can also specify that an action can only be executed at certain\ntimes of the day, or from certain IP addresses, and so forth. Each\ncondition is created once at startup then passed a context to check\nevery time an action is checked to see if it can be executed.\n\n    The base class for conditions is the [Workflow::Condition](https://metacpan.org/pod/Workflow%3A%3ACondition) class.\n\n- **validator** - An action can specify one or more validators to ensure\nthat the data available to the action is correct. The data to check\ncan be as simple or complicated as you like. Each validator is created\nonce then passed a context and data to check every time an action is\nexecuted.\n\n    The base class for validators is the [Workflow::Validator](https://metacpan.org/pod/Workflow%3A%3AValidator) class.\n\n# WORKFLOW BASICS\n\n## Just a Bunch of States\n\nA workflow is just a bunch of states with rules on how to move between\nthem. These are known as transitions and are triggered by some sort of\nevent. A state is just a description of object properties. You can\ndescribe a surprisingly large number of processes as a series of\nstates and actions to move between them. The application shipped with\nthis distribution uses a fairly common application to illustrate: the\ntrouble ticket.\n\nWhen you create a workflow you have one action available to you:\ncreate a new ticket ('create issue'). The workflow has a state\n'INITIAL' when it is first created, but this is just a bootstrapping\nexercise since the workflow must always be in some state.\n\nThe workflow action 'create issue' has a property 'resulting\\_state',\nwhich just means: if you execute me properly the workflow will be in\nthe new state 'CREATED'.\n\nAll this talk of 'states' and 'transitions' can be confusing, but just\nmatch them to what happens in real life -- you move from one action to\nanother and at each step ask: what happens next?\n\nYou create a trouble ticket: what happens next? Anyone can add\ncomments to it and attach files to it while administrators can edit it\nand developers can start working on it. Adding comments does not\nreally change what the ticket is, it just adds\ninformation. Attachments are the same, as is the admin editing the\nticket.\n\nBut when someone starts work on the ticket, that is a different\nmatter. When someone starts work they change the answer to: what\nhappens next? Whenever the answer to that question changes, that means\nthe workflow has changed state.\n\n## Discover Information from the Workflow\n\nIn addition to declaring what the resulting state will be from an\naction the action also has a number of 'field' properties that\ndescribe that data it required to properly execute it.\n\nThis is an example of discoverability. This workflow system is setup\nso you can ask it what you can do next as well as what is required to\nmove on. So to use our ticket example we can do this, creating the\nworkflow and asking it what actions we can execute right now:\n\n    my $wf = Workflow::$self-\u003e_factory()-\u003ecreate_workflow( 'Ticket' );\n    my @actions = $wf-\u003eget_current_actions;\n\nWe can also interrogate the workflow about what fields are necessary\nto execute a particular action:\n\n    print \"To execute the action 'create issue' you must provide:\\n\\n\";\n    my @fields = $wf-\u003eget_action_fields( 'create issue' );\n    foreach my $field ( @fields ) {\n        print $field-\u003ename, \" (Required? \", $field-\u003eis_required, \")\\n\",\n              $field-\u003edescription, \"\\n\\n\";\n    }\n\n## Provide Information to the Workflow\n\nTo allow the workflow to run into multiple environments we must have a\ncommon way to move data between your application, the workflow and the\ncode that moves it from one state to another.\n\nWhenever the [Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory) creates a new workflow it associates\nthe workflow with a [Workflow::Context](https://metacpan.org/pod/Workflow%3A%3AContext) object. The context is what\nmoves the data from your application to the workflow and the workflow\nactions.\n\nFor instance, the workflow has no idea what the 'current user' is. Not\nonly is it unaware from an application standpoint but it does not\npresume to know where to get this information. So you need to tell it,\nand you do so through the context.\n\nThe fact that the workflow system proscribes very little means it can\nbe used in lots of different applications and interfaces. If a system\nis too closely tied to an interface (like the web) then you have to\ncreate some potentially ugly hacks to create a more convenient avenue\nfor input to your system (such as an e-mail approving a document).\n\nThe [Workflow::Context](https://metacpan.org/pod/Workflow%3A%3AContext) object is extremely simple to use -- you ask\na workflow for its context and just get/set parameters on it:\n\n    # Get the username from the Apache object\n    my $username = $r-\u003econnection-\u003euser;\n\n    # ...set it in the context\n    $wf-\u003econtext-\u003eparam( user =\u003e $username );\n\n    # somewhere else you'll need the username:\n\n    $news_object-\u003e{created_by} = $wf-\u003econtext-\u003eparam( 'user' );\n\n## Controlling What Gets Executed\n\nA typical process for executing an action is:\n\n- Get data from the user\n- Fetch a workflow\n- Set the data from the user to the workflow context\n- Execute an action on the context\n\nWhen you execute the action a number of checks occur. The action needs\nto ensure:\n\n- The data presented to it are valid -- date formats, etc. This is done\nwith a validator, more at [Workflow::Validator](https://metacpan.org/pod/Workflow%3A%3AValidator)\n- The environment meets certain conditions -- user is an administrator,\netc. This is done with a condition, more at [Workflow::Condition](https://metacpan.org/pod/Workflow%3A%3ACondition)\n\nOnce the action passes these checks and successfully executes we\nupdate the permanent workflow storage with the new state, as long as\nthe application has declared it.\n\n# WORKFLOWS ARE OBSERVABLE\n\n## Purpose\n\nIt's useful to have your workflow generate events so that other parts\nof a system can see what's going on and react. For instance, say you\nhave a new user creation process. You want to email the records of all\nusers who have a first name of 'Sinead' because you're looking for\nyour long-lost sister named 'Sinead'. You'd create an observer class\nlike:\n\n    package FindSinead;\n\n    sub update {\n        my ( $class, $wf, $event, $event_args ) = @_;\n        return unless ( $event eq 'state change' );\n        return unless ( $event_args-\u003e{to} eq 'CREATED' );\n        my $context = $wf-\u003econtext;\n        return unless ( $context-\u003eparam( 'first_name' ) eq 'Sinead' );\n\n        my $user = $context-\u003eparam( 'user' );\n        my $username = $user-\u003eusername;\n        my $email    = $user-\u003eemail;\n        my $mailer = get_mailer( ... );\n        $mailer-\u003esend( 'foo@bar.com','Found her!',\n                       \"We found Sinead under '$username' at '$email' );\n    }\n\nAnd then associate it with your workflow:\n\n    \u003cworkflow\u003e\n        \u003ctype\u003eSomeFlow\u003c/type\u003e\n        \u003cobserver class=\"FindSinead\" /\u003e\n        ...\n\nEvery time you create/fetch a workflow the associated observers are\nattached to it.\n\n## Events Generated\n\nYou can attach listeners to workflows and catch events at a few points\nin the workflow lifecycle; these are the events fired:\n\n- **create** - Issued after a workflow is first created.\n\n    No additional parameters.\n\n- **fetch** - Issued after a workflow is fetched from the persister.\n\n    No additional parameters.\n\n- **startup** - Issued at the beginning of the execute loop, before the\nfirst action is called.\n\n    No additional parameters.\n\n- **finalize** - Issued at the end of the execute loop, after all action\nare handled.\n\n    No additional parameters.\n\n- **run** - Issued before a single action is executed. Will be followed by\neither a `save` or `rollback` event.\n\n    No additional parameters.\n\n- **save** - Issued after the workflow was saved after running a single action.\n\n    No additional parameters.\n\n- **rollback** - Issued after the execution of a single action failed.\n\n    No additional parameters.\n\n- **completed** - Issued after a single action was successfully executed and\nsaved.\n\n    Receives a hashref as second parameter holding the keys `state` and\n    `action`. `$state` includes the state of the workflow before the action\n    was executed, `$action` is the action name that was executed.\n\n- **state change** - Issued after a single action is successfully executed,\nsaved and results in a state change. The event will not be fired if\nyou executed an action that did not result in a state change.\n\n    Receives a hashref as second parameter. The key `from` holds the name\n    of the state before the action, `action` is the name of the action\n    that was executed and `to` holding the name of the target (current) state.\n\n- **add history** - Issued after one or more history objects were added to\na workflow object.\n\n    The additional argument is an arrayref of all [Workflow::History](https://metacpan.org/pod/Workflow%3A%3AHistory)\n    objects added to the workflow. (Note that these will not be persisted\n    until the workflow is persisted.)\n\n## Configuring\n\nYou configure the observers directly in the 'workflow' configuration\nitem. Each 'observer' may have either a 'class' or 'sub' entry within\nit that defines the observer's location.\n\nWe load these classes at startup time. So if you specify an observer\nthat doesn't exist you see the error when the workflow system is\ninitialized rather than the system tries to use the observer.\n\nFor instance, the following defines two observers:\n\n    \u003cworkflow\u003e\n      \u003ctype\u003eObservedItem\u003c/type\u003e\n      \u003cdescription\u003eThis is...\u003c/description\u003e\n\n      \u003cobserver class=\"SomeObserver\" /\u003e\n      \u003cobserver sub=\"SomeOtherObserver::Functions::other_sub\" /\u003e\n\nIn the first declaration we specify the class ('SomeObserver') that\nwill catch observations using its `update()` method. In the second\nwe're naming exactly the subroutine ('other\\_sub()' in the class\n'SomeOtherObserver::Functions') that will catch observations.\n\nAll configured observers get all events. It's up to each observer to\nfigure out what it wants to handle.\n\n# WORKFLOW METHODS\n\nThe following documentation is for the workflow object itself rather\nthan the entire system.\n\n## Object Methods\n\n### execute\\_action( $action\\_name, $args )\n\nExecute the action `$action_name`. Typically this changes the state\nof the workflow. If `$action_name` is not in the current state, fails\none of the conditions on the action, or fails one of the validators on\nthe action an exception is thrown.\n\nThe `$args` provided, are checked against the validators to ensure the\ncontext remains in a valid state; upon successful validation, the `$args`\nare merged into the context and the action is executed as described above.\n\nAfter the action has been successfully executed and the workflow saved\nwe issue a 'execute' observation with the old state, action name and\nan autorun flag as additional parameters.\nSo if you wanted to write an observer you could create a\nmethod with the signature:\n\n    sub update {\n        my ( $class, $workflow, $action, $old_state, $action_name )\n           = @_;\n        if ( $action eq 'execute' ) { .... }\n    }\n\nWe also issue a 'change state' observation if the executed action\nresulted in a new state. See [\"WORKFLOWS ARE OBSERVABLE\"](#workflows-are-observable) above for how\nwe use and register observers.\n\nReturns: new state of workflow\n\n### get\\_current\\_actions( $group )\n\nReturns a list of action names available from the current state for\nthe given environment. So if you keep your `context()` the same if\nyou call `execute_action()` with one of the action names you should\nnot trigger any condition error since the action has already been\nscreened for conditions.\nIf you want to divide actions in groups (for example state change group,\napproval group, which have to be shown at different places on the page) add group property\nto your action\n\n    \u003caction name=\"terminate request\"  group=\"state change\"  class=\"MyApp::Action::Terminate\" /\u003e\n    \u003caction name=\"approve request\"  group=\"approval\"  class=\"MyApp::Action::Approve\" /\u003e\n\n    my @actions = $wf-\u003eget_current_actions(\"approval\");\n\n`$group` should be string that reperesents desired group name. In @actions you will get\nlist of action names available from the current state for the given environment limited by group.\n`$group` is optional parameter.\n\nReturns: list of strings representing available actions\n\n### get\\_all\\_actions\n\nReturns a list of ALL action names defined for the current state, weather or not\nthey are available from the current environment.\n\nReturns: list of strings representing available actions\n\n### get\\_action( $action\\_name )\n\nRetrieves the action object associated with `$action_name` in the\ncurrent workflow state. This will throw an exception if:\n\n- No workflow state exists with a name of the current state. (This is\nusually some sort of configuration error and should be caught at\ninitialization time, so it should not happen.)\n- No action `$action_name` exists in the current state.\n- No action `$action_name` exists in the workflow universe.\n- One of the conditions for the action in this state is not met.\n\n### get\\_action\\_fields( $action\\_name )\n\nReturn a list of [Workflow::InputField](https://metacpan.org/pod/Workflow%3A%3AInputField) objects for the given\n`$action_name`. If `$action_name` not in the current state or not\naccessible by the environment an exception is thrown.\n\nReturns: list of [Workflow::InputField](https://metacpan.org/pod/Workflow%3A%3AInputField) objects\n\n### add\\_history( @( \\\\%params | $wf\\_history\\_object ) )\n\nAdds any number of histories to the workflow, typically done by an\naction in `execute_action()` or one of the observers of that\naction. This history will not be saved until `execute_action()` is\ncomplete.\n\nYou can add a list of either hashrefs with history information in them\nor full [Workflow::History](https://metacpan.org/pod/Workflow%3A%3AHistory) objects. Trying to add anything else will\nresult in an exception and **none** of the items being added.\n\nSuccessfully adding the history objects results in a 'add history'\nobservation being thrown. See [\"WORKFLOWS ARE OBSERVABLE\"](#workflows-are-observable) above for\nmore.\n\nReturns: nothing\n\n### get\\_history()\n\nReturns list of history objects for this workflow. Note that some may\nbe unsaved if you call this during the `execute_action()` process.\n\n### get\\_unsaved\\_history()\n\nReturns list of all unsaved history objects for this workflow.\n\n### clear\\_history()\n\nClears all transient history objects from the workflow object, **not**\nfrom the long-term storage.\n\n### set( $property, $value )\n\nMethod used to overwrite [Class::Accessor](https://metacpan.org/pod/Class%3A%3AAccessor) so only certain callers can set\nproperties caller has to be a [Workflow](https://metacpan.org/pod/Workflow) namespace package.\n\nSets property to value or throws [Workflow::Exception](https://metacpan.org/pod/Workflow%3A%3AException)\n\n## Observer methods\n\n### add\\_observer( @observers )\n\nAdds one or more observers to a `Workflow` instance. An observer is a\nfunction. See [\"notify\\_observers\"](#notify_observers) for its calling convention.\n\nThis function is used internally by `Workflow::Factory` to implement\nobservability as documented in the section [\"WORKFLOWS ARE OBSERVABLE\"](#workflows-are-observable)\n\n### notify\\_observers( @arguments )\n\nCalls all observer functions registered through `add_observer` with\nthe workflow as the first argument and `@arguments` as the remaining\narguments:\n\n    $observer-\u003e( $wf, @arguments );\n\nUsed by various parts of the library to notify observers of workflow\ninstance related events.\n\n## Properties\n\nUnless otherwise noted, properties are **read-only**.\n\n### Configuration Properties\n\nSome properties are set in the configuration file for each\nworkflow. These remain static once the workflow is instantiated.\n\n#### **type**\n\nType of workflow this is. You may have many individual workflows\nassociated with a type or you may have many different types\nrunning in a single workflow engine.\n\n#### **description**\n\nDescription (usually brief, hopefully with a URL...)  of this\nworkflow.\n\n#### **time\\_zone**\n\nWorkflow uses the DateTime module to create all date objects. The time\\_zone\nparameter allows you to pass a time zone value directly to the DateTime\nnew method for all cases where Workflow needs to create a date object.\nSee the DateTime module for acceptable values.\n\n### Dynamic Properties\n\nYou can get the following properties from any workflow object.\n\n#### **id**\n\nID of this workflow. This will **always** be defined, since when the\n[Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory) creates a new workflow it first saves it to\nlong-term storage.\n\n#### **state**\n\nThe current state of the workflow.\n\n#### **last\\_update** (read-write)\n\nDate of the workflow's last update.\n\n#### **last\\_action\\_executed** (read)\n\nContains the name of the action that was tried to be executed last, even if\nthe execution could not be completed due to e.g. failed parameter validation,\nexecption on code execution. Useful to find the step that failed when using\nautorun sequences, as `state` will return the state from which it was called.\n\n### context (read-write, see below)\n\nA [Workflow::Context](https://metacpan.org/pod/Workflow%3A%3AContext) object associated with this workflow. This\nshould never be undefined as the [Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory) sets an empty\ncontext into the workflow when it is instantiated.\n\nIf you add a context to a workflow and one already exists, the values\nfrom the new workflow will overwrite values in the existing\nworkflow. This is a shallow merge, so with the following:\n\n    $wf-\u003econtext-\u003eparam( drinks =\u003e [ 'coke', 'pepsi' ] );\n    my $context = Workflow::Context-\u003enew();\n    $context-\u003eparam( drinks =\u003e [ 'beer', 'wine' ] );\n    $wf-\u003econtext( $context );\n    print 'Current drinks: ', join( ', ', @{ $wf-\u003econtext-\u003eparam( 'drinks' ) } );\n\nYou will see:\n\n    Current drinks: beer, wine\n\n## Internal Methods\n\n### init( $id, $current\\_state, \\\\%workflow\\_config, \\\\@wf\\_states )\n\n**THIS SHOULD ONLY BE CALLED BY THE** [Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory). Do not call\nthis or the `new()` method yourself -- you will only get an\nexception. Your only interface for creating and fetching workflows is\nthrough the factory.\n\nThis is called by the inherited constructor and sets the\n`$current_state` value to the property `state` and uses the other\nnon-state values from `\\%config` to set parameters via the inherited\n`param()`.\n\n### \\_get\\_workflow\\_state( \\[ $state \\] )\n\nReturn the [Workflow::State](https://metacpan.org/pod/Workflow%3A%3AState) object corresponding to `$state`, which\ndefaults to the current state.\n\n### \\_set\\_workflow\\_state( $wf\\_state )\n\nAssign the [Workflow::State](https://metacpan.org/pod/Workflow%3A%3AState) object `$wf_state` to the workflow.\n\n### \\_get\\_next\\_state( $action\\_name )\n\nReturns the name of the next state given the action\n`$action_name`. Throws an exception if `$action_name` not contained\nin the current state.\n\n## Initial workflow history\n\nWhen creating an initial [Workflow::History](https://metacpan.org/pod/Workflow%3A%3AHistory) record when creating a workflow,\nseveral fields are required.\n\n### get\\_initial\\_history\\_data\n\nThis method returns a _list_ of key/value pairs to add in the initial history\nrecord. The following defaults are returned:\n\n- `user`\n\n    value: \"n/a\"\n\n- `description`\n\n    value: \"Create new workflow\"\n\n- `action`\n\n    value: \"Create workflow\"\n\nOverride this method to change the values from their defaults. E.g.\n\n    sub get_initial_history_data {\n       return (\n            user =\u003e 1,\n            description =\u003e \"none\",\n            action =\u003e \"run\"\n       );\n    }\n\n# CONFIGURATION AND ENVIRONMENT\n\nThe configuration of Workflow is done using the format of your choice, currently\nXML and Perl are implemented, but additional formats can be added. Please refer\nto [Workflow::Config](https://metacpan.org/pod/Workflow%3A%3AConfig), for implementation details.\n\n## Configuration examples\n\n### XML configuration\n\n    \u003cworkflow\u003e\n        \u003ctype\u003emyworkflow\u003c/type\u003e\n        \u003cclass\u003eMy::Workflow\u003c/class\u003e                     \u003c!-- optional --\u003e\n        \u003cinitial_state\u003eINITIAL\u003c/initial_state\u003e          \u003c!-- optional --\u003e\n        \u003ctime_zone\u003elocal\u003c/time_zone\u003e                    \u003c!-- optional --\u003e\n        \u003cdescription\u003eThis is my workflow.\u003c/description\u003e \u003c!-- optional --\u003e\n\n        \u003c!-- List one or more states --\u003e\n        \u003cstate name=\"INITIAL\"\u003e\n            \u003caction name=\"upload file\" resulting_state=\"uploaded\" /\u003e\n            \u003caction name=\"cancel upload\" resulting_state=\"finished\" /\u003e\n        \u003c/state\u003e\n\n        \u003cstate name=\"uploaded\"\u003e\n            \u003caction name=\"verify file\"\u003e\n               \u003cresulting_state return=\"redo\"     state=\"INITIAL\" /\u003e\n               \u003cresulting_state return=\"finished\" state=\"finished\"/\u003e\n            \u003c/action\u003e\n        \u003c/state\u003e\n\n        \u003cstate name=\"finished\" /\u003e\n    \u003c/workflow\u003e\n\n## Logging\n\nAs of version 2.0, Workflow allows application developers to select their own\nlogging solution of preference: The library is a [Log::Any](https://metacpan.org/pod/Log%3A%3AAny) log producer. See\n[Log::Any::Adapter](https://metacpan.org/pod/Log%3A%3AAny%3A%3AAdapter) for examples on how to configure logging. For those\nwanting to keep running their [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) configuration, please install\n[Log::Any::Adapter::Log4perl](https://metacpan.org/pod/Log%3A%3AAny%3A%3AAdapter%3A%3ALog4perl) and add one `use` statement and one line after\nthe initialization of `Log::Log4perl`:\n\n    use Log::Log4perl;\n    use Log::Any::Adapter;   # Add this additional use-statement\n\n    Log::Log4perl::init('/etc/log4perl.conf');\n    Log::Any::Adapter-\u003eset( 'Log4perl' ); # Additional: Log::Any initialization\n\n# DEPENDENCIES\n\nThe full list of dependencies is specified in the cpanfile in the distribution\narchive. Additional dependencies are listed by feature. The following features\nare currently supported by this distribution:\n\n- `examples`\n\n    The additional dependencies required to run the example applications.\n\n# BUGS AND LIMITATIONS\n\nKnown bugs and limitations can be seen in the Github issue tracker:\n\n[https://github.com/perl-workflow/perl-workflow/issues](https://github.com/perl-workflow/perl-workflow/issues)\n\n# BUG REPORTING\n\nBug reporting should be done either via Github issues\n\n[https://github.com/perl-workflow/perl-workflow/issues](https://github.com/perl-workflow/perl-workflow/issues)\n\nA list of currently known issues can be seen via the same URL.\n\n# TEST\n\nThe test suite can be run using [prove](https://metacpan.org/pod/prove)\n\n    % prove --lib\n\nSome of the tests are reserved for the developers and are only run of the\nenvironment variable TEST\\_AUTHOR is set to true. Requirements for these tests\nwill only be installed through [Dist::Zilla](https://metacpan.org/pod/Dist%3A%3AZilla)'s `authordeps` command:\n\n    % dzil authordeps --missing | cpanm --notest\n\nThe test to verify the (http/https) links in the POD documentation will only\nrun when the variable POD\\_LINKS is set.\n\n# CODING STYLE\n\nCurrently the code is formatted using [Perl::Tidy](https://metacpan.org/pod/Perl%3A%3ATidy). The resource file can be\ndownloaded from the central repository.\n\n        notes/perltidyrc\n\n# PROJECT\n\nThe Workflow project is currently hosted on GitHub\n\n- GitHub: [https://github.com/perl-workflow/perl-workflow](https://github.com/perl-workflow/perl-workflow)\n\n## REPOSITORY\n\nThe code is kept under revision control using Git:\n\n- [https://github.com/perl-workflow/perl-workflow/tree/master/](https://github.com/perl-workflow/perl-workflow/tree/master/)\n\n## OTHER RESOURCES\n\n- MetaCPAN\n\n    [https://metacpan.org/release/Workflow](https://metacpan.org/release/Workflow)\n\n# COPYRIGHT\n\nCopyright (c) 2003 Chris Winters and Arvato Direct;\nCopyright (c) 2004-2021 Chris Winters. All rights reserved.\n\nThis library is free software; you can redistribute it and/or modify\nit under the same terms as Perl itself.\n\n# AUTHORS\n\nJonas B. (jonasbn) \u003cjonasbn@cpan.org\u003e, current maintainer.\n\nChris Winters \u003cchris@cwinters.com\u003e, original author.\n\nThe following folks have also helped out (listed here in no particular order):\n\nThanks for to Michiel W. Beijen for fix to badly formatted URL, included in release 1.52\n\nSeveral PRs (13 to be exact) from Erik Huelsmann resulting in release 1.49. Yet another\nbatch of PRs resulted in release 1.50\n\nPR from Mohammad S Anwar correcting some POD errors, included in release 1.49\n\nBug report from Petr Pisar resulted in release 1.48\n\nBug report from Tina Müller (tinita) resulted in release 1.47\n\nBug report from Slaven Rezić resulting in maintenance release 1.45\n\nFeature and bug fix by dtikhonov resulting in 1.40 (first pull request on Github)\n\nSérgio Alves, patch to timezone handling for workflow history deserialized using\nDBI persister resulting in 1.38\n\nHeiko Schlittermann for context serialization patch resulting in 1.36\n\nScott Harding, for lazy evaluation of conditions and for nested conditions, see\nChanges file: 1.35\n\nOliver Welter, patch implementing custom workflows, see Changes file: 1.35 and\npatch related to this in 1.37 and factory subclassing also in 1.35. Improvements\nin logging for condition validation in 1.43 and 1.44 and again a patch resulting\nin release 1.46\n\nSteven van der Vegt, patch for autorun in initial state and improved exception\nhandling for validators, see Changes file: 1.34\\_1\n\nAndrew O'Brien, patch implementing dynamic reloaded of flows, see Changes file:\n1.33\n\nSergei Vyshenski, bug reports - addressed and included in 1.33, Sergei also\nmaintains the FreeBSD port\n\nAlejandro Imass, improvements and clarifications, see Changes file: 1.33\n\nDanny Sadinoff, patches to give better control of initial state and history\nrecords for workflow, see Changes file: 1.33\n\nThomas Erskine, for patch adding new accessors and fixing several bugs see\nChanges file 1.33\n\nIvan Paponov, for patch implementing action groups, see Changes file, 1.33\n\nRobert Stockdale, for patch implementing dynamic names for conditions, see\nChanges file, 1.32\n\nJim Brandt, for patch to Workflow::Config::XML. See Changes file, 0.27 and 0.30\n\nAlexander Klink, for: patches resulting in 0.23, 0.24, 0.25, 0.26 and 0.27\n\nMichael Bell, for patch resulting in 0.22\n\nMartin Bartosch, for bug reporting and giving the solution not even using a\npatch (0.19 to 0.20) and a patch resulting in 0.21\n\nRandal Schwartz, for testing 0.18 and swiftly giving feedback (0.18 to 0.19)\n\nChris Brown, for a patch to [Workflow::Config::Perl](https://metacpan.org/pod/Workflow%3A%3AConfig%3A%3APerl) (0.17 to 0.18)\n\nDietmar Hanisch \u003cDietmar.Hanisch@Bertelsmann.de\u003e - Provided\nmost of the good ideas for the module and an excellent example of\neveryday use.\n\nTom Moertel \u003ctmoertel@cpan.org\u003e gave me the idea for being\nable to attach event listeners (observers) to the process.\n\nMichael Roberts \u003cmichael@vivtek.com\u003e graciously released the\n'Workflow' namespace on CPAN.\n\nMichael Schwern \u003cschwern@pobox.org\u003e barked via RT about a\ndependency problem and CPAN naming issue.\n\nJim Smith \u003cjgsmith@tamu.edu\u003e - Contributed patches (being able\nto subclass [Workflow::Factory](https://metacpan.org/pod/Workflow%3A%3AFactory)) and good ideas.\n\nMartin Winkler \u003cmw@arsnavigandi.de\u003e - Pointed out a bug and a\nfew other items.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperl-workflow%2Fperl-workflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fperl-workflow%2Fperl-workflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperl-workflow%2Fperl-workflow/lists"}