Passed
Push — master ( fa70e8...a6a555 )
by Daniele
02:35
created

FluidInsertionHandler   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 378
Duplicated Lines 2.12 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 95.51%
Metric Value
wmc 60
lcom 1
cbo 2
dl 8
loc 378
ccs 149
cts 156
cp 0.9551
rs 6.0975

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B insertElement() 0 25 5
A newContext() 0 4 1
C handleOptionals() 0 24 7
A handleInsertion() 0 21 3
C handleStringMixed() 0 29 7
B handleIntegerMixed() 4 23 5
C handleIntegerDocument() 4 32 8
B createElement() 0 34 4
A attachNodes() 0 15 4
A insertSpecialContent() 0 16 1
A insertSpecialAttribute() 0 10 1
A insertStringSimple() 0 10 1
A insertStringMixed() 0 16 1
A insertIntegerArray() 0 15 2
A insertIntegerString() 0 10 1
B insertIntegerXml() 0 28 2
A insertIntegerDomdocument() 0 10 1
A insertIntegerDomnodelist() 0 4 1
A insertIntegerDomnode() 0 4 1
A insertIntegerSimplexml() 0 4 1
A insertIntegerFluidxml() 0 4 1
A insertIntegerFluidcontext() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FluidInsertionHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FluidInsertionHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FluidXml;
4
5
class FluidInsertionHandler
6
{
7
        private $document;
8
        private $dom;
9
        private $namespaces;
10
11 1
        public function __construct($document)
12
        {
13 1
                $this->document   = $document;
14 1
                $this->dom        = $document->dom;
15 1
                $this->namespaces =& $document->namespaces;
16 1
        }
17
18 1
        public function insertElement(&$nodes, $element, &$optionals, $fn, $orig_context)
19
        {
20 1
                list($element, $attributes, $switch_context) = $this->handleOptionals($element, $optionals);
21
22 1
                $new_nodes = [];
23
24 1
                foreach ($nodes as $n) {
25 1
                        foreach ($element as $k => $v) {
26 1
                                $cx        = $this->handleInsertion($n, $k, $v, $fn, $optionals);
27 1
                                $new_nodes = \array_merge($new_nodes, $cx);
28
                        }
29
                }
30
31 1
                $new_context = $this->newContext($new_nodes);
32
33
                // Setting the attributes is an help that the addChild method
34
                // offers to the user and is the same of:
35
                // 1. appending a child switching the context
36
                // 2. setting the attributes over the new context.
37 1
                if (! empty($attributes)) {
38 1
                        $new_context->setAttribute($attributes);
39
                }
40
41 1
                return $switch_context ? $new_context : $orig_context;
42
        }
43
44 1
        protected function newContext(&$context)
45
        {
46 1
                return new FluidContext($this->document, $this, $context);
47
        }
48
49 1
        protected function handleOptionals($element, &$optionals)
50
        {
51 1
                if (! \is_array($element)) {
52 1
                        $element = [ $element ];
53
                }
54
55 1
                $switch_context = false;
56 1
                $attributes     = [];
57
58 1
                foreach ($optionals as $opt) {
59 1
                        if (\is_array($opt)) {
60 1
                                $attributes = $opt;
61
62
                        } elseif (\is_bool($opt)) {
63 1
                                $switch_context = $opt;
64
65 1
                        } elseif (\is_string($opt) || \is_numeric($opt)) {
66 1
                                $e = \array_pop($element);
67 1
                                $element[$e] = $opt;
68
                        }
69
                }
70
71 1
                return [ $element, $attributes, $switch_context ];
72
        }
73
74
75 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
76
        {
77
                // This is an highly optimized method.
78
                // Good code design would split this method in many different handlers
79
                // each one with its own checks. But it is too much expensive in terms
80
                // of performances for a core method like this, so this implementation
81
                // is prefered to collapse many identical checks to one.
82
83 1
                $handlers = ['handleStringMixed', 'handleIntegerMixed', 'handleIntegerDocument'];
84
85 1
                $status = false;
86 1
                foreach ($handlers as $handler) {
87 1
                        $return = $this->$handler($parent, $k, $v, $fn, $optionals, $status);
88
89 1
                        if ($status === true) {
90 1
                                return $return;
91
                        }
92
                }
93
94 1
                throw new \Exception('Input type not supported.');
95
        }
96
97 1
        protected function handleStringMixed($parent, $k, $v, $fn, &$optionals, &$status)
98
        {
99 1
                if (! \is_string($k)) {
100 1
                        return;
101
                }
102
103 1
                $handler = 'insertStringMixed';
104
105 1
                if ($k[0] === '@') {
106 1
                        $handler = 'insertSpecialAttribute';
107
108 1
                        if ($k === '@') {
109 1
                                $handler = 'insertSpecialContent';
110
                        }
111
                } else {
112 1
                        if (\is_string($v)) {
113 1
                                if (! FluidHelper::isAnXmlString($v)) {
114 1
                                        $handler = 'insertStringSimple';
115
                                }
116
                        } else {
117 1
                                if (\is_numeric($v)) {
118 1
                                        $handler = 'insertStringSimple';
119
                                }
120
                        }
121
                }
122
123 1
                $status = true;
124 1
                return $this->$handler($parent, $k, $v, $fn, $optionals);
125
        }
126
127 1
        protected function handleIntegerMixed($parent, $k, $v, $fn, &$optionals, &$status)
128
        {
129
                // if (! \is_integer($k)) {
130
                //         return;
131
                // }
132
133 1
                $handler = null;
134
135 1
                if (\is_string($v)) {
136 1
                        if (FluidHelper::isAnXmlString($v)) {
137 1
                                $handler = 'insertIntegerXml';
138
                        } else {
139 1
                                $handler = 'insertIntegerString';
140
                        }
141
                } elseif (\is_array($v)) {
142 1
                        $handler = 'insertIntegerArray';
143
                }
144
145 1 View Code Duplication
                if ($handler !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146 1
                        $status = true;
147 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
148
                }
149 1
        }
150
151 1
        protected function handleIntegerDocument($parent, $k, $v, $fn, &$optionals, &$status)
152
        {
153
                // if (! \is_integer($k)) {
154
                //         return;
155
                // }
156
157 1
                $handler = null;
158
159 1
                if ($v instanceof \DOMDocument) {
160 1
                        $handler = 'insertIntegerDomdocument';
161
162
                } elseif ($v instanceof \DOMNodeList) {
163 1
                        $handler = 'insertIntegerDomnodelist';
164
165
                } elseif ($v instanceof \DOMNode) {
166 1
                        $handler = 'insertIntegerDomnode';
167
168
                } elseif ($v instanceof \SimpleXMLElement) {
169 1
                        $handler = 'insertIntegerSimplexml';
170
171
                } elseif ($v instanceof FluidXml) {
172 1
                        $handler = 'insertIntegerFluidxml';
173
174
                } elseif ($v instanceof FluidContext) {
175 1
                        $handler = 'insertIntegerFluidcontext';
176
                }
177
178 1 View Code Duplication
                if ($handler !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179 1
                        $status = true;
180 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
181
                }
182 1
        }
183
184 1
        protected function createElement($name, $value = null)
185
        {
186
                // The DOMElement instance must be different for every node,
187
                // otherwise only one element is attached to the DOM.
188
189 1
                $id  = null;
190 1
                $uri = null;
191
192
                // The node name can contain the namespace id prefix.
193
                // Example: xsl:template
194 1
                $colon_pos = \strpos($name, ':');
195
196 1
                if ($colon_pos !== false) {
197 1
                        $id   = \substr($name, 0, $colon_pos);
198 1
                        $name = \substr($name, $colon_pos + 1);
199
                }
200
201 1
                if ($id !== null) {
202 1
                        $ns  = $this->namespaces[$id];
203 1
                        $uri = $ns->uri();
204
205 1
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
206 1
                                $name = "{$id}:{$name}";
207
                        }
208
                }
209
210
                // Algorithm 1:
211 1
                $el = new \DOMElement($name, $value, $uri);
212
213
                // Algorithm 2:
214
                // $el = $dom->createElement($name, $value);
215
216 1
                return $el;
217
        }
218
219 1
        protected function attachNodes($parent, $nodes, $fn)
220
        {
221 1
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
222 1
                        $nodes = [ $nodes ];
223
                }
224
225 1
                $context = [];
226
227 1
                foreach ($nodes as $el) {
228 1
                        $el        = $this->dom->importNode($el, true);
229 1
                        $context[] = $fn($parent, $el);
230
                }
231
232 1
                return $context;
233
        }
234
235 1
        protected function insertSpecialContent($parent, $k, $v)
236
        {
237
                // The user has passed an element text content:
238
                // [ '@' => 'Element content.' ]
239
240
                // Algorithm 1:
241 1
                $this->newContext($parent)->addText($v);
242
243
                // Algorithm 2:
244
                // $this->setText($v);
245
246
                // The user can specify multiple '@' special elements
247
                // so Algorithm 1 is the right choice.
248
249 1
                return [];
250
        }
251
252 1
        protected function insertSpecialAttribute($parent, $k, $v)
253
        {
254
                // The user has passed an attribute name and an attribute value:
255
                // [ '@attribute' => 'Attribute content' ]
256
257 1
                $attr = \substr($k, 1);
258 1
                $this->newContext($parent)->setAttribute($attr, $v);
259
260 1
                return [];
261
        }
262
263 1
        protected function insertStringSimple($parent, $k, $v, $fn)
264
        {
265
                // The user has passed an element name and an element value:
266
                // [ 'element' => 'Element content' ]
267
268 1
                $el = $this->createElement($k, $v);
269 1
                $el = $fn($parent, $el);
270
271 1
                return [ $el ];
272
        }
273
274 1
        protected function insertStringMixed($parent, $k, $v, $fn, &$optionals)
275
        {
276
                // The user has passed one of these cases:
277
                // - [ 'element' => [...] ]
278
                // - [ 'element' => '<xml>...</xml>' ]
279
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
280
281 1
                $el = $this->createElement($k);
282 1
                $el = $fn($parent, $el);
283
284
                // The new children elements must be created in the order
285
                // they are supplied, so 'addChild' is the perfect operation.
286 1
                $this->newContext($el)->addChild($v, ...$optionals);
287
288 1
                return [ $el ];
289
        }
290
291 1
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
292
        {
293
                // The user has passed a wrapper array:
294
                // [ [...], ... ]
295
296 1
                $context = [];
297
298 1
                foreach ($v as $kk => $vv) {
299 1
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
300
301 1
                        $context = \array_merge($context, $cx);
302
                }
303
304 1
                return $context;
305
        }
306
307 1
        protected function insertIntegerString($parent, $k, $v, $fn)
308
        {
309
                // The user has passed a node name without a node value:
310
                // [ 'element', ... ]
311
312 1
                $el = $this->createElement($v);
313 1
                $el = $fn($parent, $el);
314
315 1
                return [ $el ];
316
        }
317
318 1
        protected function insertIntegerXml($parent, $k, $v, $fn)
319
        {
320
                // The user has passed an XML document instance:
321
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
322
323 1
                $wrapper = new \DOMDocument();
324 1
                $wrapper->formatOutput       = true;
325 1
                $wrapper->preserveWhiteSpace = false;
326
327 1
                $v = \ltrim($v);
328
329 1
                if ($v[1] === '?') {
330 1
                        $wrapper->loadXML($v);
331 1
                        $nodes = $wrapper->childNodes;
332
                } else {
333
                        // A way to import strings with multiple root nodes.
334 1
                        $wrapper->loadXML("<root>$v</root>");
335
336
                        // Algorithm 1:
337 1
                        $nodes = $wrapper->documentElement->childNodes;
338
339
                        // Algorithm 2:
340
                        // $xp = new \DOMXPath($wrapper);
341
                        // $nodes = $xp->query('/root/*');
342
                }
343
344 1
                return $this->attachNodes($parent, $nodes, $fn);
345
        }
346
347 1
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
348
        {
349
                // A DOMDocument can have multiple root nodes.
350
351
                // Algorithm 1:
352 1
                return $this->attachNodes($parent, $v->childNodes, $fn);
353
354
                // Algorithm 2:
355
                // return $this->attachNodes($parent, $v->documentElement, $fn);
356
        }
357
358 1
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
359
        {
360 1
                return $this->attachNodes($parent, $v, $fn);
361
        }
362
363 1
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
364
        {
365 1
                return $this->attachNodes($parent, $v, $fn);
366
        }
367
368 1
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
369
        {
370 1
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
371
        }
372
373 1
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
374
        {
375 1
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
376
        }
377
378 1
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
379
        {
380 1
                return $this->attachNodes($parent, $v->array(), $fn);
381
        }
382
}
383