Completed
Push — master ( 96d573...f9f049 )
by Ehsan
07:54
created

XmlReferenceDumper::writeNode()   F

Complexity

Conditions 45
Paths > 20000

Size

Total Lines 205
Code Lines 115

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 45
eloc 115
nc 1087104
nop 4
dl 0
loc 205
rs 2
c 0
b 0
f 0

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
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Config\Definition\Dumper;
13
14
use Symfony\Component\Config\Definition\ConfigurationInterface;
15
use Symfony\Component\Config\Definition\NodeInterface;
16
use Symfony\Component\Config\Definition\ArrayNode;
17
use Symfony\Component\Config\Definition\EnumNode;
18
use Symfony\Component\Config\Definition\PrototypedArrayNode;
19
20
/**
21
 * Dumps a XML reference configuration for the given configuration/node instance.
22
 *
23
 * @author Wouter J <[email protected]>
24
 */
25
class XmlReferenceDumper
26
{
27
    private $reference;
28
29
    public function dump(ConfigurationInterface $configuration, $namespace = null)
30
    {
31
        return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
32
    }
33
34
    public function dumpNode(NodeInterface $node, $namespace = null)
35
    {
36
        $this->reference = '';
37
        $this->writeNode($node, 0, true, $namespace);
38
        $ref = $this->reference;
39
        $this->reference = null;
40
41
        return $ref;
42
    }
43
44
    /**
45
     * @param NodeInterface $node
46
     * @param int           $depth
47
     * @param bool          $root      If the node is the root node
48
     * @param string        $namespace The namespace of the node
49
     */
50
    private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
51
    {
52
        $rootName = ($root ? 'config' : $node->getName());
53
        $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
54
55
        // xml remapping
56
        if ($node->getParent()) {
57
            $remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
58
                return $rootName === $mapping[1];
59
            });
60
61
            if (count($remapping)) {
62
                list($singular) = current($remapping);
63
                $rootName = $singular;
64
            }
65
        }
66
        $rootName = str_replace('_', '-', $rootName);
67
68
        $rootAttributes = array();
69
        $rootAttributeComments = array();
70
        $rootChildren = array();
71
        $rootComments = array();
72
73
        if ($node instanceof ArrayNode) {
74
            $children = $node->getChildren();
75
76
            // comments about the root node
77
            if ($rootInfo = $node->getInfo()) {
78
                $rootComments[] = $rootInfo;
79
            }
80
81
            if ($rootNamespace) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rootNamespace of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
82
                $rootComments[] = 'Namespace: '.$rootNamespace;
83
            }
84
85
            // render prototyped nodes
86
            if ($node instanceof PrototypedArrayNode) {
87
                $prototype = $node->getPrototype();
88
89
                $info = 'prototype';
90
                if (null !== $prototype->getInfo()) {
91
                    $info .= ': '.$prototype->getInfo();
92
                }
93
                array_unshift($rootComments, $info);
94
95
                if ($key = $node->getKeyAttribute()) {
96
                    $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
97
                }
98
99
                if ($prototype instanceof PrototypedArrayNode) {
100
                    $prototype->setName($key);
101
                    $children = array($key => $prototype);
102
                } elseif ($prototype instanceof ArrayNode) {
103
                    $children = $prototype->getChildren();
104
                } else {
105
                    if ($prototype->hasDefaultValue()) {
106
                        $prototypeValue = $prototype->getDefaultValue();
107
                    } else {
108
                        switch (get_class($prototype)) {
109
                            case 'Symfony\Component\Config\Definition\ScalarNode':
110
                                $prototypeValue = 'scalar value';
111
                                break;
112
113
                            case 'Symfony\Component\Config\Definition\FloatNode':
114
                            case 'Symfony\Component\Config\Definition\IntegerNode':
115
                                $prototypeValue = 'numeric value';
116
                                break;
117
118
                            case 'Symfony\Component\Config\Definition\BooleanNode':
119
                                $prototypeValue = 'true|false';
120
                                break;
121
122
                            case 'Symfony\Component\Config\Definition\EnumNode':
123
                                $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
124
                                break;
125
126
                            default:
127
                                $prototypeValue = 'value';
128
                        }
129
                    }
130
                }
131
            }
132
133
            // get attributes and elements
134
            foreach ($children as $child) {
135
                if (!$child instanceof ArrayNode) {
136
                    // get attributes
137
138
                    // metadata
139
                    $name = str_replace('_', '-', $child->getName());
140
                    $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
141
142
                    // comments
143
                    $comments = array();
144
                    if ($info = $child->getInfo()) {
145
                        $comments[] = $info;
146
                    }
147
148
                    if ($example = $child->getExample()) {
149
                        $comments[] = 'Example: '.$example;
150
                    }
151
152
                    if ($child->isRequired()) {
153
                        $comments[] = 'Required';
154
                    }
155
156
                    if ($child instanceof EnumNode) {
157
                        $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
158
                    }
159
160
                    if (count($comments)) {
161
                        $rootAttributeComments[$name] = implode(";\n", $comments);
162
                    }
163
164
                    // default values
165
                    if ($child->hasDefaultValue()) {
166
                        $value = $child->getDefaultValue();
167
                    }
168
169
                    // append attribute
170
                    $rootAttributes[$name] = $value;
171
                } else {
172
                    // get elements
173
                    $rootChildren[] = $child;
174
                }
175
            }
176
        }
177
178
        // render comments
179
180
        // root node comment
181
        if (count($rootComments)) {
182
            foreach ($rootComments as $comment) {
183
                $this->writeLine('<!-- '.$comment.' -->', $depth);
184
            }
185
        }
186
187
        // attribute comments
188
        if (count($rootAttributeComments)) {
189
            foreach ($rootAttributeComments as $attrName => $comment) {
190
                $commentDepth = $depth + 4 + strlen($attrName) + 2;
191
                $commentLines = explode("\n", $comment);
192
                $multiline = (count($commentLines) > 1);
193
                $comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
194
195
                if ($multiline) {
196
                    $this->writeLine('<!--', $depth);
197
                    $this->writeLine($attrName.': '.$comment, $depth + 4);
198
                    $this->writeLine('-->', $depth);
199
                } else {
200
                    $this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
201
                }
202
            }
203
        }
204
205
        // render start tag + attributes
206
        $rootIsVariablePrototype = isset($prototypeValue);
207
        $rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype);
208
        $rootOpenTag = '<'.$rootName;
209
        if (1 >= ($attributesCount = count($rootAttributes))) {
210
            if (1 === $attributesCount) {
211
                $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
212
            }
213
214
            $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
215
216
            if ($rootIsVariablePrototype) {
217
                $rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
0 ignored issues
show
Bug introduced by
The variable $prototypeValue does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
218
            }
219
220
            $this->writeLine($rootOpenTag, $depth);
221
        } else {
222
            $this->writeLine($rootOpenTag, $depth);
223
224
            $i = 1;
225
226
            foreach ($rootAttributes as $attrName => $attrValue) {
227
                $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
228
229
                $this->writeLine($attr, $depth + 4);
230
231
                if ($attributesCount === $i++) {
232
                    $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
233
234
                    if ($rootIsVariablePrototype) {
235
                        $rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
236
                    }
237
                }
238
            }
239
        }
240
241
        // render children tags
242
        foreach ($rootChildren as $child) {
243
            $this->writeLine('');
244
            $this->writeNode($child, $depth + 4);
245
        }
246
247
        // render end tag
248
        if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
249
            $this->writeLine('');
250
251
            $rootEndTag = '</'.$rootName.'>';
252
            $this->writeLine($rootEndTag, $depth);
253
        }
254
    }
255
256
    /**
257
     * Outputs a single config reference line.
258
     *
259
     * @param string $text
260
     * @param int    $indent
261
     */
262
    private function writeLine($text, $indent = 0)
263
    {
264
        $indent = strlen($text) + $indent;
265
        $format = '%'.$indent.'s';
266
267
        $this->reference .= sprintf($format, $text).PHP_EOL;
268
    }
269
270
    /**
271
     * Renders the string conversion of the value.
272
     *
273
     * @param mixed $value
274
     *
275
     * @return string
276
     */
277
    private function writeValue($value)
278
    {
279
        if ('%%%%not_defined%%%%' === $value) {
280
            return '';
281
        }
282
283
        if (is_string($value) || is_numeric($value)) {
284
            return $value;
285
        }
286
287
        if (false === $value) {
288
            return 'false';
289
        }
290
291
        if (true === $value) {
292
            return 'true';
293
        }
294
295
        if (null === $value) {
296
            return 'null';
297
        }
298
299
        if (empty($value)) {
300
            return '';
301
        }
302
303
        if (is_array($value)) {
304
            return implode(',', $value);
305
        }
306
    }
307
}
308