Completed
Push — develop ( 38b232...8588db )
by Peter
02:23
created

Loop::preLoopInternal()   D

Complexity

Conditions 13
Paths 434

Size

Total Lines 70
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 3 Features 0
Metric Value
c 7
b 3
f 0
dl 0
loc 70
rs 4.0321
cc 13
eloc 42
nc 434
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) {
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
            $varTranslator = $helper->getVarTranslator();
138
            $translation->offsetSet(
139
                $spec['loop']['base'],
140
                $varTranslator->getTranslation()->offsetGet($spec['loop']['base'])
141
            );
142
        }
143
144
        $items = array_slice(
145
            (array) $translation->fetch($spec['loop']['base']),
146
            $spec['loop']['offset'],
147
            $spec['loop']['length'],
148
            true
149
        );
150
151
        if (empty($items)) {
152
            $this->nothingToLoop($arg);
153
            return;
154
        }
155
156
        empty($spec['loop']['shuffle'])
157
            or shuffle($items);
158
159
        $this->instructionsRenderer
160
            ->expandInstructions($spec, $translation)
161
            ->expandInstructions($spec['loop'], $translation);
162
163
        // TODO spec object
164
        $arg->setSpec($spec);
165
        $this->nodesLoop($nodes, $items, $arg);
166
    }
167
168
    /**
169
     * @param PluginArgument $arg
170
     */
171
    protected function nothingToLoop(PluginArgument $arg)
172
    {
173
        $spec = $arg->getSpec();
174
        if (!array_key_exists('onEmpty', $spec['loop'])) {
175
            return;
176
        }
177
178
        $helper = $arg->getHelper();
179
        $nodes  = $arg->getNodes();
180
        $translation = $arg->getTranslation();
181
        $onEmptySpec = $spec['loop']['onEmpty'];
182
183
        if (!empty($onEmptySpec['locator'])) {
184
            $this->instructionsRenderer->subInstructions($nodes, [$onEmptySpec], $translation);
185
            return;
186
        }
187
188
        $this->instructionsRenderer->expandInstructions($onEmptySpec, $translation);
189
        empty($onEmptySpec['instructions'])
190
            or $this->instructionsRenderer->subInstructions(
191
                $nodes,
192
                $onEmptySpec['instructions'],
193
                $translation
194
            );
195
196
        $helper->manipulateNodes($nodes, $onEmptySpec, $translation);
197
    }
198
199
    /**
200
     * @param NodeList $nodes
201
     * @param array $items
202
     * @param PluginArgument $arg
203
     * @return self
204
     */
205
    protected function nodesLoop(NodeList $nodes, array $items, PluginArgument $arg)
206
    {
207
        $spec = $arg->getSpec();
208
        $translation = $arg->getTranslation();
209
210
        foreach ($nodes as $node) {
211
212
            $beforeNode = $node->nextSibling ? $node->nextSibling : null;
213
            $nodeClone  = clone $node;
214
            $parentNode = $node->parentNode;
215
            
216
            if (empty($node->parentNode)) {
217
                continue;
218
            }
219
220
            $node->parentNode->removeChild($node);
221
222
            $loopArg = new ArrayObject([
223
                'spec'   => $spec,
224
                'vars'   => $translation,
225
                'target' => $this,
226
                'parentNode' => $parentNode,
227
                'beforeNode' => $beforeNode,
228
            ]);
229
230
            $this->itemsLoop($items, $arg, $loopArg, $nodeClone);
231
232
            empty($spec['instructions'])
233
                or $this->instructionsRenderer->subInstructions(
234
                    $nodes->create([$node]),
235
                    $spec['instructions'],
236
                    $translation
237
                );
238
        }
239
240
        return $this;
241
    }
242
243
    /**
244
     * @param array $items
245
     * @param PluginArgument $arg
246
     * @param ArrayObject $loopArg
247
     * @param Element $nodeClone
248
     * @return self
249
     */
250
    protected function itemsLoop(array $items, PluginArgument $arg, ArrayObject $loopArg, Element $nodeClone)
251
    {
252
        $spec   = $arg->getSpec();
253
        $helper = $arg->getHelper();
254
        $nodes  = $arg->getNodes();
255
        $translation = $arg->getTranslation();
256
257
        $loopArg['index'] = !empty($spec['loop']['index']) ? $spec['loop']['index'] : 0;
258
        foreach ($items as $key => $item) {
259
            $loopArg['index']++;
260
261
            $loopArg['key']  = $key;
262
            $loopArg['item'] = (array) $item;
263
            $loopArg['node'] = clone $nodeClone;
264
265
            if ($this->invokeLoopHelpers($spec['loop'], $loopArg)) {
266
                continue;
267
            }
268
269
            $loopArg['item'][$translation->makeExtraVarKey('key')]   = (string) $loopArg['key'];
270
            $loopArg['item'][$translation->makeExtraVarKey('index')] = (string) $loopArg['index'];
271
272
            // create local translation
273
            $localTranslation = clone $translation;
274
            $localTranslation->mergeValues($loopArg['item']);
275
276
            // add node
277
            if ($loopArg['beforeNode']) {
278
                $loopArg['parentNode']->insertBefore($loopArg['node'], $loopArg['beforeNode']);
279
            } else {
280
                $loopArg['parentNode']->appendChild($loopArg['node']);
281
            }
282
283
            // manipulate item nodes with local spec and translation
284
            $localSpec = $spec;
285
            unset($localSpec['loop']);
286
            $helper->manipulateNodes(
287
                $nodes->create([$loopArg['node']]),
288
                $localSpec,
289
                $localTranslation
290
            );
291
292
            // render sub-instructions
293
            empty($spec['loop']['instructions'])
294
                or $this->instructionsRenderer->subInstructions(
295
                    $nodes->create([$loopArg['node']]),
296
                    $spec['loop']['instructions'],
297
                    $localTranslation
298
                );
299
        }
300
301
        return $this;
302
    }
303
304
    /**
305
     * @param array $spec
306
     * @param ArrayObject $loopArg
307
     * @return bool
308
     */
309
    protected function invokeLoopHelpers(array $spec, ArrayObject $loopArg)
310
    {
311
        if (empty($spec['helper'])) {
312
            return false;
313
        }
314
315
        foreach ((array) $spec['helper'] as $helperOptions) {
316
            $this->invokeLoopHelper((array) $helperOptions, $loopArg);
317
318
            if (empty($loopArg['node'])) {
319
                // allows to skip node
320
                return true;
321
            }
322
        }
323
324
        return false;
325
    }
326
327
    /**
328
     * @param array $helperOptions
329
     * @param ArrayObject $loopArg
330
     * @return self
331
     * @throws Exception\InvalidLoopHelperException
332
     */
333
    protected function invokeLoopHelper(array $helperOptions, ArrayObject $loopArg)
334
    {
335
        $helper = current($helperOptions);
336
        if (is_string($helper)) {
337
            if (!$this->loopHelpers->has($helper)) {
338
                throw new Exception\InvalidLoopHelperException('Loop helper `' . $helper . '` not found');
339
            }
340
341
            $loopHelper = $this->loopHelpers->get($helper);
342
            $loopHelper($loopArg, (array) next($helperOptions));
343
            return $this;
344
345
        } elseif (is_callable($helper)) {
346
            call_user_func($helper, $loopArg, (array) next($helperOptions));
347
            return $this;
348
        }
349
350
        throw new Exception\InvalidLoopHelperException('Loop helper can\'t execute');
351
    }
352
}
353