Total Complexity | 59 |
Total Lines | 262 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like XmlReferenceDumper 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.
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 XmlReferenceDumper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class XmlReferenceDumper |
||
31 | { |
||
32 | private ?string $reference = null; |
||
33 | |||
34 | public function dump(ConfigurationInterface $configuration, ?string $namespace = null): string |
||
35 | { |
||
36 | return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); |
||
37 | } |
||
38 | |||
39 | public function dumpNode(NodeInterface $node, ?string $namespace = null): string |
||
40 | { |
||
41 | $this->reference = ''; |
||
42 | $this->writeNode($node, 0, true, $namespace); |
||
43 | $ref = $this->reference; |
||
44 | $this->reference = null; |
||
45 | |||
46 | return $ref; |
||
47 | } |
||
48 | |||
49 | private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, ?string $namespace = null): void |
||
50 | { |
||
51 | $rootName = ($root ? 'config' : $node->getName()); |
||
52 | $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); |
||
53 | |||
54 | // xml remapping |
||
55 | if ($node->getParent()) { |
||
|
|||
56 | $remapping = array_filter($node->getParent()->getXmlRemappings(), fn (array $mapping) => $rootName === $mapping[1]); |
||
57 | |||
58 | if (\count($remapping)) { |
||
59 | [$singular] = current($remapping); |
||
60 | $rootName = $singular; |
||
61 | } |
||
62 | } |
||
63 | $rootName = str_replace('_', '-', $rootName); |
||
64 | |||
65 | $rootAttributes = []; |
||
66 | $rootAttributeComments = []; |
||
67 | $rootChildren = []; |
||
68 | $rootComments = []; |
||
69 | |||
70 | if ($node instanceof ArrayNode) { |
||
71 | $children = $node->getChildren(); |
||
72 | |||
73 | // comments about the root node |
||
74 | if ($rootInfo = $node->getInfo()) { |
||
75 | $rootComments[] = $rootInfo; |
||
76 | } |
||
77 | |||
78 | if ($rootNamespace) { |
||
79 | $rootComments[] = 'Namespace: '.$rootNamespace; |
||
80 | } |
||
81 | |||
82 | // render prototyped nodes |
||
83 | if ($node instanceof PrototypedArrayNode) { |
||
84 | $prototype = $node->getPrototype(); |
||
85 | |||
86 | $info = 'prototype'; |
||
87 | if (null !== $prototype->getInfo()) { |
||
88 | $info .= ': '.$prototype->getInfo(); |
||
89 | } |
||
90 | array_unshift($rootComments, $info); |
||
91 | |||
92 | if ($key = $node->getKeyAttribute()) { |
||
93 | $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key; |
||
94 | } |
||
95 | |||
96 | if ($prototype instanceof PrototypedArrayNode) { |
||
97 | $prototype->setName($key ?? ''); |
||
98 | $children = [$key => $prototype]; |
||
99 | } elseif ($prototype instanceof ArrayNode) { |
||
100 | $children = $prototype->getChildren(); |
||
101 | } else { |
||
102 | if ($prototype->hasDefaultValue()) { |
||
103 | $prototypeValue = $prototype->getDefaultValue(); |
||
104 | } else { |
||
105 | $prototypeValue = match ($prototype::class) { |
||
106 | ScalarNode::class => 'scalar value', |
||
107 | FloatNode::class, |
||
108 | IntegerNode::class => 'numeric value', |
||
109 | BooleanNode::class => 'true|false', |
||
110 | EnumNode::class => $prototype->getPermissibleValues('|'), |
||
111 | default => 'value', |
||
112 | }; |
||
113 | } |
||
114 | } |
||
115 | } |
||
116 | |||
117 | // get attributes and elements |
||
118 | foreach ($children as $child) { |
||
119 | if ($child instanceof ArrayNode) { |
||
120 | // get elements |
||
121 | $rootChildren[] = $child; |
||
122 | |||
123 | continue; |
||
124 | } |
||
125 | |||
126 | // get attributes |
||
127 | |||
128 | // metadata |
||
129 | $name = str_replace('_', '-', $child->getName()); |
||
130 | $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world |
||
131 | |||
132 | // comments |
||
133 | $comments = []; |
||
134 | if ($child instanceof BaseNode && $info = $child->getInfo()) { |
||
135 | $comments[] = $info; |
||
136 | } |
||
137 | |||
138 | if ($child instanceof BaseNode && $example = $child->getExample()) { |
||
139 | $comments[] = 'Example: '.(\is_array($example) ? implode(', ', $example) : $example); |
||
140 | } |
||
141 | |||
142 | if ($child->isRequired()) { |
||
143 | $comments[] = 'Required'; |
||
144 | } |
||
145 | |||
146 | if ($child instanceof BaseNode && $child->isDeprecated()) { |
||
147 | $deprecation = $child->getDeprecation($child->getName(), $node->getPath()); |
||
148 | $comments[] = \sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); |
||
149 | } |
||
150 | |||
151 | if ($child instanceof EnumNode) { |
||
152 | $comments[] = 'One of '.$child->getPermissibleValues('; '); |
||
153 | } |
||
154 | |||
155 | if (\count($comments)) { |
||
156 | $rootAttributeComments[$name] = implode(";\n", $comments); |
||
157 | } |
||
158 | |||
159 | // default values |
||
160 | if ($child->hasDefaultValue()) { |
||
161 | $value = $child->getDefaultValue(); |
||
162 | } |
||
163 | |||
164 | // append attribute |
||
165 | $rootAttributes[$name] = $value; |
||
166 | } |
||
167 | } |
||
168 | |||
169 | // render comments |
||
170 | |||
171 | // root node comment |
||
172 | if (\count($rootComments)) { |
||
173 | foreach ($rootComments as $comment) { |
||
174 | $this->writeLine('<!-- '.$comment.' -->', $depth); |
||
175 | } |
||
176 | } |
||
177 | |||
178 | // attribute comments |
||
179 | if (\count($rootAttributeComments)) { |
||
180 | foreach ($rootAttributeComments as $attrName => $comment) { |
||
181 | $commentDepth = $depth + 4 + \strlen($attrName) + 2; |
||
182 | $commentLines = explode("\n", $comment); |
||
183 | $multiline = (\count($commentLines) > 1); |
||
184 | $comment = implode(\PHP_EOL.str_repeat(' ', $commentDepth), $commentLines); |
||
185 | |||
186 | if ($multiline) { |
||
187 | $this->writeLine('<!--', $depth); |
||
188 | $this->writeLine($attrName.': '.$comment, $depth + 4); |
||
189 | $this->writeLine('-->', $depth); |
||
190 | } else { |
||
191 | $this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth); |
||
192 | } |
||
193 | } |
||
194 | } |
||
195 | |||
196 | // render start tag + attributes |
||
197 | $rootIsVariablePrototype = isset($prototypeValue); |
||
198 | $rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype); |
||
199 | $rootOpenTag = '<'.$rootName; |
||
200 | if (1 >= ($attributesCount = \count($rootAttributes))) { |
||
201 | if (1 === $attributesCount) { |
||
202 | $rootOpenTag .= \sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); |
||
203 | } |
||
204 | |||
205 | $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; |
||
206 | |||
207 | if ($rootIsVariablePrototype) { |
||
208 | $rootOpenTag .= $prototypeValue.'</'.$rootName.'>'; |
||
209 | } |
||
210 | |||
211 | $this->writeLine($rootOpenTag, $depth); |
||
212 | } else { |
||
213 | $this->writeLine($rootOpenTag, $depth); |
||
214 | |||
215 | $i = 1; |
||
216 | |||
217 | foreach ($rootAttributes as $attrName => $attrValue) { |
||
218 | $attr = \sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); |
||
219 | |||
220 | $this->writeLine($attr, $depth + 4); |
||
221 | |||
222 | if ($attributesCount === $i++) { |
||
223 | $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth); |
||
224 | |||
225 | if ($rootIsVariablePrototype) { |
||
226 | $rootOpenTag .= $prototypeValue.'</'.$rootName.'>'; |
||
227 | } |
||
228 | } |
||
229 | } |
||
230 | } |
||
231 | |||
232 | // render children tags |
||
233 | foreach ($rootChildren as $child) { |
||
234 | $this->writeLine(''); |
||
235 | $this->writeNode($child, $depth + 4); |
||
236 | } |
||
237 | |||
238 | // render end tag |
||
239 | if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { |
||
240 | $this->writeLine(''); |
||
241 | |||
242 | $rootEndTag = '</'.$rootName.'>'; |
||
243 | $this->writeLine($rootEndTag, $depth); |
||
244 | } |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Outputs a single config reference line. |
||
249 | */ |
||
250 | private function writeLine(string $text, int $indent = 0): void |
||
251 | { |
||
252 | $indent = \strlen($text) + $indent; |
||
253 | $format = '%'.$indent.'s'; |
||
254 | |||
255 | $this->reference .= \sprintf($format, $text).\PHP_EOL; |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Renders the string conversion of the value. |
||
260 | */ |
||
261 | private function writeValue(mixed $value): string |
||
292 | } |
||
293 | } |
||
294 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.