Completed
Push — master ( 11bfa6...c50688 )
by Oliver
02:33
created

StateCollectionMerger::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 4
     */
33
    public function __construct(StateCollectionInterface $targetCollection)
34 4
    {
35 4
        $this->targetCollection = $targetCollection;
36
    }
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 4
     */
67
    protected function createState($name)
68 4
    {
69
        return new State($name);
70
    }
71
72
    /**
73
     * @param StateInterface      $sourceState
74
     * @param TransitionInterface $sourceTransition
75
     *
76
     * @throws \InvalidArgumentException
77 4
     */
78
    protected function addTransition(StateInterface $sourceState, TransitionInterface $sourceTransition)
79 4
    {
80 4
        if ($sourceState instanceof State) {
81 4
            $sourceState->addTransition($sourceTransition);
82
        } else {
83
            throw new \InvalidArgumentException('Overwrite this method to implement a different type!');
84 4
        }
85
    }
86
87
    /**
88
     * @param TransitionInterface $sourceTransition
89
     *
90
     * @throws \InvalidArgumentException
91
     *
92
     * @return \Metabor\Statemachine\Transition
93 4
     */
94
    protected function createTransition(TransitionInterface $sourceTransition)
95 4
    {
96 4
        $targetStateName = $sourceTransition->getTargetState()->getName();
97 4
        $targetState = $this->findOrCreateState($targetStateName);
98 4
        $this->mergeMetadata($sourceTransition->getTargetState(), $targetState);
99
        $eventName = $sourceTransition->getEventName();
100 4
101 3
        if ($sourceTransition->getConditionName()) {
102 3
            if ($sourceTransition instanceof Transition) {
103 3
                $condition = $sourceTransition->getCondition();
104
            } else {
105
                throw new \InvalidArgumentException('Overwrite this method to implement a different type!');
106 3
            }
107 4
        } else {
108
            $condition = null;
109
        }
110 4
        
111
        if ($sourceTransition instanceof WeightedInterface) {
112
            $transition->setWeight($sourceTransition->getWeight());
0 ignored issues
show
Bug introduced by
The variable $transition does not exist. Did you mean $sourceTransition?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
113
        }
114
115
        return new Transition($targetState, $eventName, $condition);
116
    }
117
118
    /**
119 4
     * @param object $source
120
     * @param object $target
121 4
     *
122 4
     * @throws \RuntimeException
123 4
     */
124 4
    protected function mergeMetadata($source, $target)
125 4
    {
126 3
        if ($source instanceof \ArrayAccess) {
127 4
            if ($target instanceof \ArrayAccess) {
128 4
                if ($source instanceof MetadataInterface) {
129
                    $metadata = $source->getMetadata();
130
                    foreach ($metadata as $offset => $value) {
131 4
                        $target->offsetSet($offset, $value);
132
                    }
133 1
                } else {
134 4
                    throw new \RuntimeException('Source had to make all metadata available!');
135 4
                }
136
            } else {
137
                throw new \RuntimeException('Source metadata can not be merged!');
138
            }
139
        }
140
    }
141
142 4
    /**
143
     * @param StateInterface $state
144 4
     *
145 4
     * @throws \InvalidArgumentException
146 4
     */
147
    protected function addState(StateInterface $state)
148
    {
149 4
        if ($this->targetCollection instanceof StateCollection) {
150
            $this->targetCollection->addState($state);
151
        } else {
152
            throw new \InvalidArgumentException('TargetCollection has to be a StateCollection. Overwrite this method to implement a different type!');
153
        }
154
    }
155
156 4
    /**
157
     * @param string $name
158 4
     *
159 4
     * @return \MetaborStd\Statemachine\StateInterface
160 4
     */
161 4
    protected function findOrCreateState($name)
162 4
    {
163 4
        $name = $this->stateNamePrefix . $name;
164
        if ($this->targetCollection->hasState($name)) {
165
            $targetState = $this->targetCollection->getState($name);
166 4
        } else {
167
            $targetState = $this->createState($name);
168
            $this->addState($targetState);
169
        }
170
171
        return $targetState;
172
    }
173
174 4
    /**
175
     * @param StateInterface $sourceState
176 4
     *
177 4
     * @throws \InvalidArgumentException
178 4
     */
179
    protected function mergeState(StateInterface $sourceState)
180
    {
181 4
        $name = $sourceState->getName();
182 4
        $targetState = $this->findOrCreateState($name);
183 4
        $this->mergeMetadata($sourceState, $targetState);
184 4
185
        /* @var $transition TransitionInterface */
186 4
        foreach ($sourceState->getTransitions() as $sourceTransition) {
187 4
            $targetTransition = $this->createTransition($sourceTransition);
188 4
            $this->addTransition($targetState, $targetTransition);
189
        }
190 4
191
        foreach ($sourceState->getEventNames() as $eventName) {
192 4
            $sourceEvent = $sourceState->getEvent($eventName);
193 3
            $targetEvent = $targetState->getEvent($eventName);
194 4
195 4
            $this->mergeMetadata($sourceEvent, $targetEvent);
196 4
197
            foreach ($sourceEvent->getObservers() as $observer) {
198
                $targetEvent->attach($observer);
199
            }
200 4
        }
201
    }
202
203 4
    /**
204 4
     */
205 4
    protected function mergeStateCollection(StateCollectionInterface $source)
206 4
    {
207
        /* @var $sourceState StateInterface */
208
        foreach ($source->getStates() as $sourceState) {
209
            $this->mergeState($sourceState);
210
        }
211 4
    }
212
213 4
    /**
214 4
     * @see \MetaborStd\MergeableInterface::merge()
215 4
     */
216
    public function merge($source)
217
    {
218
        if ($source instanceof StateCollectionInterface) {
219
            $this->mergeStateCollection($source);
220
        } elseif ($source instanceof StateInterface) {
221
            $this->mergeState($source);
222
        } elseif ($source instanceof \Traversable) {
223
            foreach ($source as $value) {
224 4
                $this->merge($value);
225
            }
226
        } else {
227
            throw new \InvalidArgumentException('Source can not be merged!');
228
        }
229
    }
230
}
231