Zeta Workflow 05: Zeta Components Interface

When using the Zeta Components package from Laravel, you will be using the Zeta Components Wrapper. In PHP (and Laravel) terms, the Zeta Components Wrapper implements the Zeta Components Interface specification. The rest of this post is a copy of the PHPDocumentor documentation inside the Zeta Components Interface.

  • The Zeta Components Interface is file workbench/star-tribune/workflow/src/StarTribune/Workflow/Wrapper/ZetaComponentsInterface.php. 
  • The PHPDocumentor-generated file is workbench/star-tribune/workflow/phpdoc/classes/StarTribune.Workflow.Wrapper.ZetaComponentsInterface.html.
  • The top of the phpdoc tree is workbench/star-tribune/workflow/phpdoc/index.html.


Zeta Components Interface

The Zeta Components Wrapper holds the workflow structure within itself, which means you need to go through wrapper functions for pretty much everything. This means more wrapper functions may need to be created as more of the underlying Zeta Components come into use.

On the other hand, you can call getWorkflow() to get a pointer to the workflow, and then manipulate it directly while it remains within the wrapper. If you find yourself doing this, consider adding wrapper methods to do what you need to have done.

This ZetaComponentsInterface is intended to strongly encourage “coding to an interface.” This allows the the wrapper functions to be split out, refactored, instrumented, etc., as needed without adjusting the interface.

The interface is bound to the implementation in the WorkflowServiceProvider. Should it ever become necessary, the Service Provider could bind different implementation versions based on a configuration parameter.

Meanwhile, let’s keep things simple. Here is the intent:

  • Document the functions intended to be externally callable here. If externally-visible functions are added, add their signature and documentation here.
  • Most methods in the implementation are marked public to allow ease of unit testing. But if they are not listed here in the interface, consider them private.
  • Expect the Service Provider to wire up the implementation. If things change, change the Service Provider.

We use the following minor naming conventions:

  • create*: Methods that build and store something internally
  • build*: Methods that build something and return it to the caller
  • “Boolean input” means integer 0 or integer 1

See the following unit test(s) as the definitive example(s) of interface usage:

  • workflow/wrapperTest.php
  • workflow/subWorkflowTest.php
  • rollback/slideExceptionTest.php
  • rollback/slideFailTest.php

createSlides(array $slides) : void

The Zeta Components Workflow is a flow diagram, where each node in the diagram is some sort of action. Under the covers, there is a complicated process which displays a slide (i.e., the Laravel view) and solicits user input from which we resume the workflow.

The “show a slide and wait for user input” step is implemented as a Zeta Components Action Node containing a callback. The callback is our class SlideProvider, and holds the name of the slide to show.

The createSlides() method takes an array of tag-value pairs, where the tag (e.g., slide1, slide2, slide3, etc.) is the name to use in setting up the workflow, and the value is the name of the slide to present (i.e., the Laravel view).

Internally, the wrapper takes care of wiring up the Action Node to the callback. You can then use the slide’s tag in place of pointing to the Action Node.

Example, assuming Laravel views are stored in $this->slideName1, $this->slideName2, $this->slideName3, $this->slideName4:

 $slides = array(
     'slide1' => $this->slideName1,
     'slide2' => $this->slideName2,
     'slide3' => $this->slideName3,
     'slide4' => $this->slideName4,
     );
 $this->wrapper->createSlides($slides);

 // Workflow start produces slide = slideName1

 $this->wrapper->addToStart('slide1');

cancel() : void

Cancel the current workflow execution, if any. Cancel means delete the execution out of the database, i.e., summarily blow it away.

createWorkflow(string $name) : void

Create a new, empty, Zeta Components Workflow. It must be saved to the tied database before executing. Each time a workflow of a given name is saved, a new workflow is stored in the tied database with the same name and a new version number.

addToStart(mixed $slide) : void

Designate the initial workflow node. The parameter can either be the name of a slide (its tag, as previously passed to createSlides()), or a Workflow Node object.

buildTrueFalseOnBooleanInput(array $parms) : void

Implement a true/false choice as workflow graph nodes. Input is a single key-value array so that parameter order does not get mixed up. Accidentally swapping the true and false paths would be disastrous!

The key-value inputs are:

  • $parms[‘variable’] is the name of the input variable whose value will be passed into the workflow after accepting user input.
  • $parms[‘inNode’] is the starting point, i.e., the name of the slide which solicited the boolean choice. This parameter can be either the slide tag or a Workflow Node.
  • $parms[‘trueNode’] is the path to take when the input value is true (i.e., integer 1). This parameter can be either the slide tag or a Workflow Node.
  • $parms[‘falseNode’] is the path to take when the input value is false (i.e., integer 0). This parameter can be either the slide tag or a Workflow Node.

buildBooleanInput(string $name) : object

Create a boolean input node. The input is required to be integer 0 or integer 1.

addSlideOutNode(mixed $slide, mixed $add) : void

Connect two nodes together. The first node can be a slide node, and can be either the slide name or a Workflow Node.

The normal use case is to connect the solicit-input node to the slide soliciting the input, although it could be used to connect any two nodes together.

The rest of this description explains how we use this function to solicit user input and resume the workflow based on that input.

In the Zeta Components Workflow, the workflow node declaring the slide to present is followed by the input node. When the workflow suspends, we read the ‘slide’ variable to see what slide to present, and present that slide. The workflow suspends at the input node. We then resume, at the input node, with the solicited input value.

The input value is a specific variable name cooked into the input node, e.g., via when calling buildBooleanInput($name). Thus the actual slide, meaning the Laravel view, needs to be tied to the specific variable name to be used.

buildMerge(array $parms) : void

Define the merging of execution paths in a Zeta Components Workflow. Note that this method is part of creating and defining the workflow graph prior to execution. This is a convenience wrapper for ezcWorkflowNodeSimpleMerge.

The key-value inputs are:

  • $parms[‘outNode’] The node to reach as a result of the merge. This can be either a slide tag or a Workflow Node object.
  • $parms[‘inNodes’] An array of two or more nodes indicating alternate paths coming in to the node. Note that the key is plural. Each array value can be either a slide tag or a Workflow Node object.

endHere(mixed $slide) : void

Declare an end to the workflow.

The workflow can have any number of endpoints. That is, each “ending” node connects to the actual End node. This method declares the specified slide or node to be an endpoint. Workflow execution will terminate upon successfully processing the named node.

saveWorkflow() : void

Save the current workflow to the tied database. Note that any workflow MUST be saved to the database prior to executing that workflow. We are not programmatically enforcing that requirement, but Bad Things Will Happen if you don’t.

You might, for example, load the workflow from an XML file, and then use this function to save it to the database. The workflow name was given when we created the workflow object, and will be saved here by that name.

loadByName(string $name) : void

Load the named workflow. We load the most recent version of the workflow as stored in the tied database.

start() : integer

Start the workflow execution from the top. The workflow MUST have been saved to the tied database. Loading the workflow from the tied database counts, given that that means it was at some point saved.

If no workflow execution is loaded, we load a fresh execution to start.

resume(array $parms, integer $id) : mixed

Resume the workflow execution. If the id is specified, we do a fresh load of that execution from the tied database. If no execution is loaded, we load an execution of the current workflow.

  • If no execution is currently loaded: We load a fresh execution. If $id was also specified, we load that execution id. If not, we load a new execution for the current workflow.
  • An execution is already loaded, but $id specified: We throw away the loaded execution, and load a new execution with this id.
  • An execution is loaded, and no $id specified: We resume the current execution.

The normal runtime flow is:

  • $id = start() which loads a fresh execution of this workflow and, if the workflow suspends, returns the execution id. An interactive workflow should therefore return a non-null $id, and a non-interactive workflow, or a completed workflow, returns a null $id.
  • resume($parms, $id) which resumes the workflow execution with user input
  • resume($parms) which resumes the same already-loaded workflow execution with additional input. This variant will normally be only be used by unit tests and other batch processes.

createRunner(integer $id)

Force loading a new workflow execution. This method will not normally be needed. Use it if you need to replace the currently-loaded execution with a different workflow execution.

getVariables() : array

Get the workflow variables from the current execution, if any. If there is no current execution, returns an empty array. Note that if we are paused in a sub workflow, this returns the variables of the main workflow NOT the sub workflow.

getVariable(string $variable) : mixed

Returns the named workflow variable, or null if that variable does not exist.

Note that this returns the variable from the main workflow NOT the sub workflow.

getActiveVariables() : array

Get the workflow variables from whichever execution (possibly a sub workflow) will resume.

getActiveVariable(string $variable) : mixed

Returns the “active” named workflow variable, or null if that variable does not exist.

addPlugin(\ezcWorkflowExecutionPlugin $plugin) : bool

Adds a plugin to this execution.

removePlugin(\ezcWorkflowExecutionPlugin $plugin) : bool

Removes a plugin from this execution.

addListener(\ezcWorkflowExecutionListener $listener) : bool

Adds a listener to this execution.

removeListener(\ezcWorkflowExecutionListener $listener) : bool

Removes a listener from this execution.

isCancelled() : bool

Returns true when the workflow execution has been cancelled.

hasEnded() : bool

Returns true when the workflow execution has ended.

isSuspended() : bool

Returns true when the workflow execution has been suspended.

Implementation

Next up is the implementation. It will be a short post, since we have already described the API: http://otscripts.com/zeta-workflow-06-zeta-components-wrapper-implementation/.