Passed
Push — master ( e5d8be...6a9acd )
by Fabrice
04:52 queued 02:37
created

Interrupter::propagate()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 37
rs 8.8333
1
<?php
2
3
/*
4
 * This file is part of NodalFlow.
5
 *     (c) Fabrice de Stefanis / https://github.com/fab2s/NodalFlow
6
 * This source file is licensed under the MIT license which you will
7
 * find in the LICENSE file or at https://opensource.org/licenses/MIT
8
 */
9
10
namespace fab2s\NodalFlow;
11
12
use fab2s\NodalFlow\Flows\FlowInterface;
13
use fab2s\NodalFlow\Flows\InterrupterInterface;
14
use fab2s\NodalFlow\Nodes\NodeInterface;
15
use InvalidArgumentException;
16
17
/**
18
 * Class Interrupter
19
 */
20
class Interrupter implements InterrupterInterface
21
{
22
    /**
23
     * @var string
24
     */
25
    protected $flowTarget;
26
27
    /**
28
     * @var string
29
     */
30
    protected $nodeTarget;
31
32
    /**
33
     * interrupt type
34
     *
35
     * @var string
36
     */
37
    protected $type;
38
39
    /**
40
     * @var array
41
     */
42
    protected $types = [
43
        InterrupterInterface::TYPE_CONTINUE => 1,
44
        InterrupterInterface::TYPE_BREAK    => 1,
45
    ];
46
47
    /**
48
     * Interrupter constructor.
49
     *
50
     * @param null|string|FlowInterface $flowTarget , target up to Targeted Flow id or InterrupterInterface::TARGET_TOP to interrupt every parent
51
     * @param null|string|NodeInterface $nodeTarget
52
     * @param null|string               $type
53
     */
54
    public function __construct($flowTarget = null, $nodeTarget = null, ?string $type = null)
55
    {
56
        $this->flowTarget = $flowTarget instanceof FlowInterface ? $flowTarget->getId() : $flowTarget;
57
        $this->nodeTarget = $nodeTarget instanceof NodeInterface ? $nodeTarget->getId() : $nodeTarget;
58
59
        if ($type !== null) {
60
            $this->setType($type);
61
        }
62
    }
63
64
    /**
65
     * @return string
66
     */
67
    public function getType(): string
68
    {
69
        return $this->type;
70
    }
71
72
    /**
73
     * @param string $type
74
     *
75
     * @throws InvalidArgumentException
76
     *
77
     * @return $this
78
     */
79
    public function setType(string $type): InterrupterInterface
80
    {
81
        if (!isset($this->types[$type])) {
82
            throw new InvalidArgumentException('type must be one of:' . implode(', ', array_keys($this->types)));
83
        }
84
85
        $this->type = $type;
86
87
        return $this;
88
    }
89
90
    /**
91
     * Trigger the Interrupt of each ancestor Flows up to a specific one, the root one
92
     * or none if :
93
     * - No FlowInterrupt is set
94
     * - FlowInterrupt is set at InterrupterInterface::TARGET_SELF
95
     * - FlowInterrupt is set at this Flow's Id
96
     * - FlowInterrupt is set as InterrupterInterface::TARGET_TOP and this has no parent
97
     *
98
     * Throw an exception if we reach the top after bubbling and FlowInterrupt != InterrupterInterface::TARGET_TOP
99
     *
100
     * @param FlowInterface $flow
101
     *
102
     * @throws NodalFlowException
103
     *
104
     * @return FlowInterface
105
     */
106
    public function propagate(FlowInterface $flow): FlowInterface
107
    {
108
        // evacuate edge cases
109
        if ($this->isEdgeInterruptCase($flow)) {
110
            // if anything had to be done, it was done first hand already
111
            // just make sure we propagate the eventual nodeTarget
112
            return $flow->setInterruptNodeId($this->nodeTarget);
113
        }
114
115
        $InterrupterFlowId = $flow->getId();
116
        if (!$this->type) {
117
            throw new NodalFlowException('No interrupt type set', 1, null, [
118
                'InterrupterFlowId' => $InterrupterFlowId,
119
            ]);
120
        }
121
122
        do {
123
            $lastFlowId = $flow->getId();
124
            if ($this->flowTarget === $lastFlowId) {
125
                // interrupting $flow
126
                return $flow->setInterruptNodeId($this->nodeTarget)->interruptFlow($this->type);
127
            }
128
129
            // Set interruptNodeId to true in order to make sure
130
            // we do not match any nodes in this flow (as it is not the target)
131
            $flow->setInterruptNodeId(true)->interruptFlow($this->type);
132
        } while ($flow->hasParent() && $flow = $flow->getParent());
133
134
        if ($this->flowTarget !== InterrupterInterface::TARGET_TOP) {
135
            throw new NodalFlowException('Interruption target missed', 1, null, [
136
                'interruptAt'       => $this->flowTarget,
137
                'InterrupterFlowId' => $InterrupterFlowId,
138
                'lastFlowId'        => $lastFlowId,
139
            ]);
140
        }
141
142
        return $flow;
143
    }
144
145
    /**
146
     * @param NodeInterface|null $node
147
     *
148
     * @return bool
149
     */
150
    public function interruptNode(NodeInterface $node = null): bool
151
    {
152
        return $node ? $this->nodeTarget === $node->getId() : false;
153
    }
154
155
    /**
156
     * @param FlowInterface $flow
157
     *
158
     * @return bool
159
     */
160
    protected function isEdgeInterruptCase(FlowInterface $flow): bool
161
    {
162
        return !$this->flowTarget ||
163
            (
164
                // asked to stop right here
165
                $this->flowTarget === InterrupterInterface::TARGET_SELF ||
166
                $this->flowTarget === $flow->getId()                    ||
167
                (
168
                    // target root when this Flow is root already
169
                    $this->flowTarget === InterrupterInterface::TARGET_TOP &&
170
                    !$flow->hasParent()
171
                )
172
            );
173
    }
174
}
175