Completed
Push — master ( ddc20e...61f12d )
by Fabrice
03:08
created

Interrupter::getType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 0
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\Nodes\NodeInterface;
14
use InvalidArgumentException;
15
16
/**
17
 * Class Interrupter
18
 */
19
class Interrupter implements InterrupterInterface
20
{
21
    /**
22
     * @var string
23
     */
24
    protected $flowTarget;
25
26
    /**
27
     * @var string
28
     */
29
    protected $nodeTarget;
30
31
    /**
32
     * interrupt type
33
     *
34
     * @var string
35
     */
36
    protected $type;
37
38
    /**
39
     * @var array
40
     */
41
    protected $types = [
42
        InterrupterInterface::TYPE_CONTINUE => 1,
43
        InterrupterInterface::TYPE_BREAK    => 1,
44
    ];
45
46
    /**
47
     * Interrupter constructor.
48
     *
49
     * @param null|string|FlowInterface $flowTarget , target up to Targeted Flow id or InterrupterInterface::TARGET_TOP to interrupt every parent
50
     * @param string|NodeInterface      $nodeTarget
0 ignored issues
show
Documentation introduced by
Should the type for parameter $nodeTarget not be string|NodeInterface|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
51
     * @param string|null               $type
52
     */
53
    public function __construct($flowTarget = null, $nodeTarget = null, $type = null)
54
    {
55
        if ($flowTarget instanceof FlowInterface) {
56
            $flowTarget = $flowTarget->getId();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $flowTarget. This often makes code more readable.
Loading history...
57
        }
58
59
        $this->flowTarget = $flowTarget;
60
61
        if ($nodeTarget instanceof NodeInterface) {
62
            $nodeTarget = $nodeTarget->getNodeHash();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $nodeTarget. This often makes code more readable.
Loading history...
63
        }
64
65
        $this->nodeTarget = $nodeTarget;
66
67
        if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
68
            $this->setType($type);
69
        }
70
    }
71
72
    /**
73
     * @return string
74
     */
75
    public function getType()
76
    {
77
        return $this->type;
78
    }
79
80
    /**
81
     * @param string $type
82
     *
83
     * @throws InvalidArgumentException
84
     *
85
     * @return $this
86
     */
87
    public function setType($type)
88
    {
89
        if (!isset($this->types[$type])) {
90
            throw new InvalidArgumentException('type must be one of:' . implode(', ', array_keys($this->types)));
91
        }
92
93
        $this->type = $type;
94
95
        return $this;
96
    }
97
98
    /**
99
     * Trigger the Interrupt of each ancestor Flows up to a specific one, the root one
100
     * or none if :
101
     * - No FlowInterrupt is set
102
     * - FlowInterrupt is set at this Flow's Id
103
     * - FlowInterrupt is set as InterrupterInterface::TARGET_TOP and this has no parent
104
     *
105
     * Throw an exception if we reach the top after bubbling and FlowInterrupt != InterrupterInterface::TARGET_TOP
106
     *
107
     * @param FlowInterface $flow
108
     *
109
     * @throws NodalFlowException
110
     *
111
     * @return FlowInterface
112
     */
113
    public function propagate(FlowInterface $flow)
114
    {
115
        $InterrupterFlowId = $flow->getId();
116
        // evacuate border cases
117
        if (
118
            // no target = InterrupterInterface::TARGET_SELF
119
            !$this->flowTarget ||
120
            (
121
                // asked to stop right here
122
                $this->flowTarget === InterrupterInterface::TARGET_SELF ||
123
                $this->flowTarget === $InterrupterFlowId ||
124
                (
125
                    // target root when this Flow is root already
126
                    $this->flowTarget === InterrupterInterface::TARGET_TOP &&
127
                    !$flow->hasParent()
128
                )
129
            )
130
        ) {
131
            // if anything had to be done, it was done first hand already
132
            // just make sure we propagate the eventual nodeTarget
133
            return $flow->setInterruptNodeId($this->nodeTarget);
134
        }
135
136
        if (!$this->type) {
137
            throw new NodalFlowException('No interrupt type set', 1, null, [
138
                'InterrupterFlowId' => $InterrupterFlowId,
139
            ]);
140
        }
141
142
        do {
143
            // keep this for later
144
            $lastFlowId = $flow->getId();
145
            if ($this->flowTarget === $lastFlowId) {
146
                // interrupting $flow
147
                return $flow->setInterruptNodeId($this->nodeTarget)->interruptFlow($this->type);
148
            }
149
150
            // by not bubbling the FlowInterrupt we make sure that each
151
            // eventual upstream Flow will only interrupt themselves, the
152
            // actual management is performed by this Flow which first
153
            // received the interrupt signal
154
            // also set interruptNodeId to true in order to make sure
155
            // we do not match any nodes in this flow (as it is not the target)
156
            $flow->setInterruptNodeId(true)->interruptFlow($this->type);
157
        } while ($flow = $flow->getParent());
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $flow. This often makes code more readable.
Loading history...
158
159
        // Here the target was either InterrupterInterface::TARGET_TOP or
160
        // anything else that did not match any of the Flow ancestor's
161
        // ids.
162
        // `$lastFlowId` is the root Flow id, which may as well be this
163
        // very Flow.
164
        // This implies that the only legit value for `$interruptAt` is
165
        // `InterrupterInterface::TARGET_TOP` since :
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
166
        // - `$interruptAt` matching this Flow id is caught above
167
        //    when triggering `isReached` the first time
168
        // - `$interruptAt` being null is also caught above
169
        // - `$interruptAt`  matching `InterrupterInterface::TARGET_TOP`
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
170
        //    with this having no parent is also caught above
171
        if ($this->flowTarget !== InterrupterInterface::TARGET_TOP) {
172
            throw new NodalFlowException('Interruption target missed', 1, null, [
173
                'interruptAt'       => $this->flowTarget,
174
                'InterrupterFlowId' => $InterrupterFlowId,
175
                'lastFlowId'        => $lastFlowId,
176
            ]);
177
        }
178
179
        return $flow;
180
    }
181
182
    /**
183
     * @param NodeInterface|null $node
184
     *
185
     * @return bool
186
     */
187
    public function interruptNode(NodeInterface $node = null)
188
    {
189
        return $node ? $this->nodeTarget === $node->getNodeHash() : false;
190
    }
191
}
192