Completed
Push — master ( 2791ea...3a522f )
by Oliver
07:11
created

StateCollectionMerger::createTransition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 6
Bugs 1 Features 0
Metric Value
c 6
b 1
f 0
dl 0
loc 16
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 10
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Metabor\Statemachine\Util;
4
5
use Metabor\Statemachine\State;
6
use Metabor\Statemachine\StateCollection;
7
use Metabor\Statemachine\Transition;
8
use MetaborStd\MergeableInterface;
9
use MetaborStd\MetadataInterface;
10
use MetaborStd\WeightedInterface;
11
use MetaborStd\Statemachine\StateCollectionInterface;
12
use MetaborStd\Statemachine\StateInterface;
13
use MetaborStd\Statemachine\TransitionInterface;
14
15
/**
16
 * @author Oliver Tischlinger
17
 */
18
class StateCollectionMerger implements MergeableInterface
19
{
20
    /**
21
     * @var StateCollectionInterface
22
     */
23
    private $targetCollection;
24
25
    /**
26
     * @var string
27
     */
28
    private $stateNamePrefix;
29
30
    /**
31
     * @param StateCollectionInterface $targetCollection
32
     */
33 4
    public function __construct(StateCollectionInterface $targetCollection)
34
    {
35 4
        $this->targetCollection = $targetCollection;
36 4
    }
37
38
    /**
39
     * @return string
40
     */
41
    public function getStateNamePrefix()
42
    {
43
        return $this->stateNamePrefix;
44
    }
45
46
    /**
47
     * @param string $stateNamePrefix
48
     */
49
    public function setStateNamePrefix($stateNamePrefix)
50
    {
51
        $this->stateNamePrefix = $stateNamePrefix;
52
    }
53
54
    /**
55
     * @return \MetaborStd\Statemachine\StateCollectionInterface
56
     */
57
    public function getTargetCollection()
58
    {
59
        return $this->targetCollection;
60
    }
61
62
    /**
63
     * @param string $name
64
     *
65
     * @return \MetaborStd\Statemachine\StateInterface
66
     */
67 4
    protected function createState($name)
68
    {
69 4
        return new State($name);
70
    }
71
72
    /**
73
     * @param StateInterface      $sourceState
74
     * @param TransitionInterface $sourceTransition
75
     *
76
     * @throws \InvalidArgumentException
77
     */
78 4
    protected function addTransition(StateInterface $sourceState, TransitionInterface $sourceTransition)
79
    {
80 4
        if ($sourceState instanceof State) {
81 4
            $sourceState->addTransition($sourceTransition);
82 4
        } else {
83
            throw new \InvalidArgumentException('Overwrite this method to implement a different type!');
84
        }
85 4
    }
86
87
    /**
88
     * @param TransitionInterface $sourceTransition
89
     * @return ConditionInterface
90
     */
91
    protected function createCondition(TransitionInterface $sourceTransition)
92
    {
93
        if ($sourceTransition->getConditionName()) {
94 4
            if ($sourceTransition instanceof Transition) {
95
                $condition = $sourceTransition->getCondition();
96 4
            } else {
97 4
                throw new \InvalidArgumentException('Overwrite this method to implement a different type!');
98 4
            }
99 4
        } else {
100
            $condition = null;
101 4
        }
102 3
103 3
        return $condition;
104 3
    }
105
106
    /**
107 3
     * @param TransitionInterface $sourceTransition
108 4
     *
109
     * @throws \InvalidArgumentException
110
     *
111 4
     * @return \Metabor\Statemachine\Transition
112 4
     */
113 4
    protected function createTransition(TransitionInterface $sourceTransition)
114 4
    {
115
        $targetStateName = $sourceTransition->getTargetState()->getName();
116 4
        $targetState = $this->findOrCreateState($targetStateName);
117
        $this->mergeMetadata($sourceTransition->getTargetState(), $targetState);
118
        $eventName = $sourceTransition->getEventName();
119
120
        $condition = $this->createCondition($sourceTransition);
121
        
122
        $transition = new Transition($targetState, $eventName, $condition);
123
        if ($sourceTransition instanceof WeightedInterface) {
124
            $transition->setWeight($sourceTransition->getWeight());
125 4
        }
126
127 4
        return $transition;
128 4
    }
129 4
130 4
    /**
131 4
     * @param object $source
132 3
     * @param object $target
133 4
     *
134 4
     * @throws \RuntimeException
135
     */
136
    protected function mergeMetadata($source, $target)
137 4
    {
138
        if ($source instanceof \ArrayAccess) {
139
            if ($target instanceof \ArrayAccess) {
140 4
                if ($source instanceof MetadataInterface) {
141 4
                    $metadata = $source->getMetadata();
142
                    foreach ($metadata as $offset => $value) {
143
                        $target->offsetSet($offset, $value);
144
                    }
145
                } else {
146
                    throw new \RuntimeException('Source had to make all metadata available!');
147
                }
148 4
            } else {
149
                throw new \RuntimeException('Source metadata can not be merged!');
150 4
            }
151 4
        }
152 4
    }
153
154
    /**
155 4
     * @param StateInterface $state
156
     *
157
     * @throws \InvalidArgumentException
158
     */
159
    protected function addState(StateInterface $state)
160
    {
161
        if ($this->targetCollection instanceof StateCollection) {
162 4
            $this->targetCollection->addState($state);
163
        } else {
164 4
            throw new \InvalidArgumentException('TargetCollection has to be a StateCollection. Overwrite this method to implement a different type!');
165 4
        }
166 4
    }
167 4
168 4
    /**
169 4
     * @param string $name
170
     *
171
     * @return \MetaborStd\Statemachine\StateInterface
172 4
     */
173
    protected function findOrCreateState($name)
174
    {
175
        $name = $this->stateNamePrefix . $name;
176
        if ($this->targetCollection->hasState($name)) {
177
            $targetState = $this->targetCollection->getState($name);
178
        } else {
179
            $targetState = $this->createState($name);
180 4
            $this->addState($targetState);
181
        }
182 4
183 4
        return $targetState;
184 4
    }
185
186
    /**
187 4
     * @param StateInterface $sourceState
188 4
     *
189 4
     * @throws \InvalidArgumentException
190 4
     */
191
    protected function mergeState(StateInterface $sourceState)
192 4
    {
193 4
        $name = $sourceState->getName();
194 4
        $targetState = $this->findOrCreateState($name);
195
        $this->mergeMetadata($sourceState, $targetState);
196 4
197
        /* @var $transition TransitionInterface */
198 4
        foreach ($sourceState->getTransitions() as $sourceTransition) {
199 3
            $targetTransition = $this->createTransition($sourceTransition);
200 4
            $this->addTransition($targetState, $targetTransition);
201 4
        }
202 4
203
        foreach ($sourceState->getEventNames() as $eventName) {
204
            $sourceEvent = $sourceState->getEvent($eventName);
205
            $targetEvent = $targetState->getEvent($eventName);
206 4
207
            $this->mergeMetadata($sourceEvent, $targetEvent);
208
209 4
            foreach ($sourceEvent->getObservers() as $observer) {
210 4
                $targetEvent->attach($observer);
211 4
            }
212 4
        }
213
    }
214
215
    /**
216
     */
217 4
    protected function mergeStateCollection(StateCollectionInterface $source)
218
    {
219 4
        /* @var $sourceState StateInterface */
220 4
        foreach ($source->getStates() as $sourceState) {
221 4
            $this->mergeState($sourceState);
222
        }
223
    }
224
225
    /**
226
     * @see \MetaborStd\MergeableInterface::merge()
227
     */
228
    public function merge($source)
229
    {
230 4
        if ($source instanceof StateCollectionInterface) {
231
            $this->mergeStateCollection($source);
232
        } elseif ($source instanceof StateInterface) {
233
            $this->mergeState($source);
234
        } elseif ($source instanceof \Traversable) {
235
            foreach ($source as $value) {
236
                $this->merge($value);
237
            }
238
        } else {
239
            throw new \InvalidArgumentException('Source can not be merged!');
240
        }
241
    }
242
}
243