Interruptions

Interruption are implemented in a way that keeps them conceptually similar to the regular continue and break operation on a loop. In both case continue and break becomes equal when the iteration is performed over one value or in our case over a fully scalar Flow. The main difference comes From the eventual presence of execution branches outside of the Interrupting Flow's ancestors. NodalFlow comes with a FlowInterrupt class, implementing FlowInterruptInterface, which create a way to accurately control the Interrupt signal propagation among Flow ancestors. When a Node issues an Interrupt signal, it is caught by is direct carrier Flow. When triggering the Interrupt, you can provide with a FlowInterrupt instance that can be set to propagate the interrupt signal up to a specific ancestor Flow. You can for example target the root Flow directly, or any of the ancestor.

Let's consider the following example Flow, composed of three Flows:

+-------------------------+-------+-----------------+
|               |-->      |       |                 |
+--Node1-->tNode|-->Node3-> bNode +-->iNode--....+-->
|RootFlow       |-->      |   |   |                 |
+---------------------------------------------------+
                          |   v   |
                          | Node1 |
                          |   |   |
                          |   v   |
                          | tNode |
                          | ----- |
                          | | | | |
                          | v v v |
                          | iNode |
                          +---+-----------------------------------+
                          |   v   |                               |
                          | bNode +-->Node1-->iNode-->NodeN--...-->
                          |   |   |                    branchFlowB|
                          +---------------------------------------+
                          |   |   |
                          |   |   |
                          |   |   |
                          |   |   |
                          |   |   |
                          |   |   | 
                          +---v---+
                          branchFlowA
iNode : InterruptNode
bNode : BranchNode
tNode : TraversableNode

In this example, RootFlow's iNode can:

branchFlowA's iNode can:

branchFlowB's iNode can:

In practice

NodalFlow comes with an InterruptNodeInterface which you can implement by extending InterruptNodeAbstract leaving you with a single method to implement :

    /**
     * @param mixed $param
     *
     * @return InterrupterInterface|null|bool `null` do do nothing, eg let the Flow proceed untouched
     *                                        `true` to trigger a continue on the carrier Flow (not ancestors)
     *                                        `false` to trigger a break on the carrier Flow (not ancestors)
     *                                        `InterrupterInterface` to trigger an interrupt to propagate up to a target (which may be one ancestor)
     */
    public function interrupt($param);

As you can see, the basics are simple, just return null to let the Flow proceed, true to skip (continue) the current record ($param) at the current point of execution or false to break the first upstream Traversable in the carrying Flow.

By returning an InterrupterInterface instance, implemented as the Interrupter class, you can accurately target any Flow and / or Node among the Node carrier Flow's ancestors.

For example, returning true is equivalent to returning:

new Interrupter(null, null, InterrupterInterface::TYPE_CONTINUE);

and returning false is equivalent to returning:

new Interrupter(null, null, InterrupterInterface::TYPE_BREAK);

The first two parameters respectively stands for Flow and Node instances or ids.

Targeting a Flow

Within an InterruptNodeInterface Node, targeting the carrier Flow can be done by either using as first parameter of the constructor:

You can target any ancestor of the carrying Flow either by Instance or Id, and you can target the root Flow directly by using InterrupterInterface::TARGET_TOP.

In each of these cases, the signal will bubble up to the targeted Flow and will: - for continue signals: skip the current record for all Nodes that may be found after the BranchNode where the signal showed up - for break signals: continue to bubble up among upstream Nodes in the target Flow until a Traversable Node is found, in which case its loop is halted, or up to the first Node of the targeted Flow in which case the Flow is halted.

If you feed the Interrupter with something that does not match any Flow among the carrier's ancestors, a NodalFlowException will be thrown.

Targeting a Node

You can additionally target a particular Node within the targeted Flow. Obviously, it will only do something if the targeted Flow is reached. You can target Node by feeding Interrupter with:

- a Node Instance in the target Flow
- a Node Id in the target Flow
- `false|null` to target the branching point on the target Flow
- `true` to target the first Node in the target Flow

There is no magic aliases like InterrupterInterface::TARGET_TOP for Nodes.

When an Interrupt signal reaches its target Flow and there is a target Node, the signal will bubble up to the targeted Node. Internally, this is done by resolving recursions without altering the record and continuing any traversable on the way up to the target where the signal is finally processed.

If a target Node was set and it was not found on the way, a NodalFlowException will be thrown.

Lowest level

Each nodes is filled with it's carrier Flow when it is attached to it. Any Node implementing NodeInterface can interrupt any Node in its carrier Flow and ancestors :

// skip this very action
$this->getCarrier()->continueFlow();
$this->getCarrier()->interruptFlow(InterrupterInterface::TYPE_CONTINUE);
// propagate skip to root Flow
$this->getCarrier()->continueFlow(new Interrupter(InterrupterInterface::TARGET_TOP));
// propagate skip to Flow id $targetFlowId
$this->getCarrier()->continueFlow(new Interrupter($targetFlowId));
// propagate skip at $targetNode  in Flow $targetFlow by instance
$this->getCarrier()->continueFlow(new Interrupter($targetFlow, $targetNode));
// same, but using lower level interruptFlow and by id
$this->getCarrier()->interruptFlow(InterrupterInterface::TYPE_CONTINUE, new Interrupter($targetFlow->getId(), $targetNode->getId())));

or

// stop the carrrier Flow right here
$this->getCarrier()->breakFlow();
$this->getCarrier()->interruptFlow(FlowInterruptInterface::TYPE_BREAK);
// ...

whenever you need to, when getTraversable() and / or exec() methods are triggered to continue or break the flow.