Completed
Push — develop ( e50d1b...c4d291 )
by Peter
02:17
created

Loop::preLoopInternal()   F

Complexity

Conditions 14
Paths 650

Size

Total Lines 71
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 4 Features 0
Metric Value
c 8
b 4
f 0
dl 0
loc 71
rs 2.8169
cc 14
eloc 43
nc 650
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Webino (http://webino.sk)
4
 *
5
 * @link        https://github.com/webino/WebinoDraw for the canonical source repository
6
 * @copyright   Copyright (c) 2012-2015 Webino, s. r. o. (http://webino.sk)
7
 * @author      Peter Bačinský <[email protected]>
8
 * @license     BSD-3-Clause
9
 */
10
11
namespace WebinoDraw\Manipulator\Plugin;
12
13
use ArrayObject;
14
use WebinoDraw\Cache\DrawCache;
15
use WebinoDraw\Dom\Element;
16
use WebinoDraw\Dom\NodeList;
17
use WebinoDraw\Draw\Helper\AbstractHelper;
18
use WebinoDraw\Draw\LoopHelperPluginManager;
19
use WebinoDraw\Exception;
20
use WebinoDraw\Instructions\InstructionsRenderer;
21
22
/**
23
 * Class Loop
24
 */
25
class Loop extends AbstractPlugin implements PreLoopPluginInterface
26
{
27
    /**
28
     * @var InstructionsRenderer
29
     */
30
    protected $instructionsRenderer;
31
32
    /**
33
     * @var LoopHelperPluginManager
34
     */
35
    protected $loopHelpers;
36
37
    /**
38
     * @var DrawCache
39
     */
40
    protected $cache;
41
42
    /**
43
     * @param InstructionsRenderer $instructionsRenderer
44
     * @param LoopHelperPluginManager $loopHelpers
45
     * @param DrawCache $cache
46
     */
47
    public function __construct(
48
        InstructionsRenderer $instructionsRenderer,
49
        LoopHelperPluginManager $loopHelpers,
50
        DrawCache $cache
51
    ) {
52
        $this->instructionsRenderer = $instructionsRenderer;
53
        $this->loopHelpers = $loopHelpers;
54
        $this->cache = $cache;
55
    }
56
57
    /**
58
     * @param PluginArgument $arg
59
     */
60
    public function preLoop(PluginArgument $arg)
61
    {
62
        $spec = $arg->getSpec();
63
        if (empty($spec['loop'])) {
64
            return;
65
        }
66
67
        if (empty($spec['cache'])) {
68
            $this->preLoopInternal($arg);
69
            return;
70
        }
71
72
        $event = $arg->getHelper()->getEvent();
73
        $nodes = $arg->getNodes();
74
75
        foreach ($nodes as $node) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
76
77
            $parentNodes = $nodes->create([$node->parentNode]);
78
            $loopEvent   = clone $event;
79
            $loopEvent->setNodes($parentNodes);
80
81
            if ($this->cache->load($loopEvent)) {
82
                continue;
83
            }
84
85
            $localArg = clone $arg;
86
            $localArg->setNodes($nodes->create([$node]));
87
88
            $this->preLoopInternal($localArg);
89
            $this->cache->save($loopEvent);
90
        }
91
    }
92
93
    /**
94
     * @param PluginArgument $arg
95
     * @throws Exception\MissingPropertyException
96
     */
97
    protected function preLoopInternal(PluginArgument $arg)
98
    {
99
        $spec = $arg->getSpec();
100
        $translation = $arg->getTranslation();
101
        $varTranslation = $translation->makeVarKeys($translation);
102
103
        $translation->containsVar($spec['loop']['base'])
104
            and $varTranslation->translate($spec['loop']['base']);
105
106
        if (empty($spec['loop']['base'])) {
107
            throw new Exception\MissingPropertyException(
108
                sprintf('Loop base expected in: %s', print_r($spec, true))
109
            );
110
        }
111
112
        $arg->stopManipulation();
113
114
        $nodes = $arg->getNodes();
115
116
        isset($spec['loop']['offset']) && $translation->containsVar($spec['loop']['offset'])
117
            and $varTranslation->translate($spec['loop']['offset']);
118
119
        // TODO spec object default
120
        empty($spec['loop']['offset'])
121
            and $spec['loop']['offset'] = 0;
122
123
        isset($spec['loop']['length']) && $translation->containsVar($spec['loop']['length'])
124
            and $varTranslation->translate($spec['loop']['length']);
125
126
        // TODO spec object default
127
        empty($spec['loop']['length'])
128
            and $spec['loop']['length'] = null;
129
130
        // TODO spec object
131
        $arg->setSpec($spec);
132
133
        $helper = $arg->getHelper();
134
        if (!$translation->offsetExists($spec['loop']['base'])
135
            && $helper instanceof AbstractHelper
136
        ) {
137
            $varTranslatorTranslation = $helper->getVarTranslator()->getTranslation();
138
            $varTranslatorTranslation->offsetExists($spec['loop']['base'])
139
                and $translation->offsetSet(
140
                    $spec['loop']['base'],
141
                    $varTranslatorTranslation->offsetGet($spec['loop']['base'])
142
                );
143
        }
144
145
        $items = array_slice(
146
            (array) $translation->fetch($spec['loop']['base']),
147
            $spec['loop']['offset'],
148
            $spec['loop']['length'],
149
            true
150
        );
151
152
        if (empty($items)) {
153
            $this->nothingToLoop($arg);
154
            return;
155
        }
156
157
        empty($spec['loop']['shuffle'])
158
            or shuffle($items);
159
160
        $this->instructionsRenderer
161
            ->expandInstructions($spec, $translation)
162
            ->expandInstructions($spec['loop'], $translation);
163
164
        // TODO spec object
165
        $arg->setSpec($spec);
166
        $this->nodesLoop($nodes, $items, $arg);
167
    }
168
169
    /**
170
     * @param PluginArgument $arg
171
     */
172
    protected function nothingToLoop(PluginArgument $arg)
173
    {
174
        $spec = $arg->getSpec();
175
        if (!array_key_exists('onEmpty', $spec['loop'])) {
176
            return;
177
        }
178
179
        $helper = $arg->getHelper();
180
        $nodes  = $arg->getNodes();
181
        $translation = $arg->getTranslation();
182
        $onEmptySpec = $spec['loop']['onEmpty'];
183
184
        if (!empty($onEmptySpec['locator'])) {
185
            $this->instructionsRenderer->subInstructions($nodes, [$onEmptySpec], $translation);
186
            return;
187
        }
188
189
        $this->instructionsRenderer->expandInstructions($onEmptySpec, $translation);
190
        empty($onEmptySpec['instructions'])
191
            or $this->instructionsRenderer->subInstructions(
192
                $nodes,
193
                $onEmptySpec['instructions'],
194
                $translation
195
            );
196
197
        $helper->manipulateNodes($nodes, $onEmptySpec, $translation);
198
    }
199
200
    /**
201
     * @param NodeList $nodes
202
     * @param array $items
203
     * @param PluginArgument $arg
204
     * @return self
205
     */
206
    protected function nodesLoop(NodeList $nodes, array $items, PluginArgument $arg)
207
    {
208
        $spec = $arg->getSpec();
209
        $translation = $arg->getTranslation();
210
211
        foreach ($nodes as $node) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
212
213
            $beforeNode = $node->nextSibling ? $node->nextSibling : null;
214
            $nodeClone  = clone $node;
215
            $parentNode = $node->parentNode;
216
            
217
            if (empty($node->parentNode)) {
218
                continue;
219
            }
220
221
            $node->parentNode->removeChild($node);
222
223
            $loopArg = new ArrayObject([
224
                'spec'   => $spec,
225
                'vars'   => $translation,
226
                'target' => $this,
227
                'parentNode' => $parentNode,
228
                'beforeNode' => $beforeNode,
229
            ]);
230
231
            $this->itemsLoop($items, $arg, $loopArg, $nodeClone);
232
233
            empty($spec['instructions'])
234
                or $this->instructionsRenderer->subInstructions(
235
                    $nodes->create([$node]),
236
                    $spec['instructions'],
237
                    $translation
238
                );
239
        }
240
241
        return $this;
242
    }
243
244
    /**
245
     * @param array $items
246
     * @param PluginArgument $arg
247
     * @param ArrayObject $loopArg
248
     * @param Element $nodeClone
249
     * @return self
250
     */
251
    protected function itemsLoop(array $items, PluginArgument $arg, ArrayObject $loopArg, Element $nodeClone)
252
    {
253
        $spec   = $arg->getSpec();
254
        $helper = $arg->getHelper();
255
        $nodes  = $arg->getNodes();
256
        $translation = $arg->getTranslation();
257
258
        $loopArg['index'] = !empty($spec['loop']['index']) ? $spec['loop']['index'] : 0;
259
        foreach ($items as $key => $item) {
260
            $loopArg['index']++;
261
262
            $loopArg['key']  = $key;
263
            $loopArg['item'] = (array) $item;
264
            $loopArg['node'] = clone $nodeClone;
265
266
            if ($this->invokeLoopHelpers($spec['loop'], $loopArg)) {
267
                continue;
268
            }
269
270
            $loopArg['item'][$translation->makeExtraVarKey('key')]   = (string) $loopArg['key'];
271
            $loopArg['item'][$translation->makeExtraVarKey('index')] = (string) $loopArg['index'];
272
273
            // create local translation
274
            $localTranslation = clone $translation;
275
            $localTranslation->mergeValues($loopArg['item']);
276
277
            // add node
278
            if ($loopArg['beforeNode']) {
279
                $loopArg['parentNode']->insertBefore($loopArg['node'], $loopArg['beforeNode']);
280
            } else {
281
                $loopArg['parentNode']->appendChild($loopArg['node']);
282
            }
283
284
            // manipulate item nodes with local spec and translation
285
            $localSpec = $spec;
286
            unset($localSpec['loop']);
287
            $helper->manipulateNodes(
288
                $nodes->create([$loopArg['node']]),
289
                $localSpec,
290
                $localTranslation
291
            );
292
293
            // render sub-instructions
294
            empty($spec['loop']['instructions'])
295
                or $this->instructionsRenderer->subInstructions(
296
                    $nodes->create([$loopArg['node']]),
297
                    $spec['loop']['instructions'],
298
                    $localTranslation
299
                );
300
        }
301
302
        return $this;
303
    }
304
305
    /**
306
     * @param array $spec
307
     * @param ArrayObject $loopArg
308
     * @return bool
309
     */
310
    protected function invokeLoopHelpers(array $spec, ArrayObject $loopArg)
311
    {
312
        if (empty($spec['helper'])) {
313
            return false;
314
        }
315
316
        foreach ((array) $spec['helper'] as $helperOptions) {
317
            $this->invokeLoopHelper((array) $helperOptions, $loopArg);
318
319
            if (empty($loopArg['node'])) {
320
                // allows to skip node
321
                return true;
322
            }
323
        }
324
325
        return false;
326
    }
327
328
    /**
329
     * @param array $helperOptions
330
     * @param ArrayObject $loopArg
331
     * @return self
332
     * @throws Exception\InvalidLoopHelperException
333
     */
334
    protected function invokeLoopHelper(array $helperOptions, ArrayObject $loopArg)
335
    {
336
        $helper = current($helperOptions);
337
        if (is_string($helper)) {
338
            if (!$this->loopHelpers->has($helper)) {
339
                throw new Exception\InvalidLoopHelperException('Loop helper `' . $helper . '` not found');
340
            }
341
342
            $loopHelper = $this->loopHelpers->get($helper);
343
            $loopHelper($loopArg, (array) next($helperOptions));
344
            return $this;
345
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
346
        } elseif (is_callable($helper)) {
347
            call_user_func($helper, $loopArg, (array) next($helperOptions));
348
            return $this;
349
        }
350
351
        throw new Exception\InvalidLoopHelperException('Loop helper can\'t execute');
352
    }
353
}
354