Loop::preLoopInternal()   F
last analyzed

Complexity

Conditions 15
Paths 651

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

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