Total Complexity | 106 |
Total Lines | 397 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like XmlDumper 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 XmlDumper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
33 | class XmlDumper extends Dumper |
||
34 | { |
||
35 | private \DOMDocument $document; |
||
36 | |||
37 | /** |
||
38 | * Dumps the service container as an XML string. |
||
39 | */ |
||
40 | public function dump(array $options = []): string |
||
41 | { |
||
42 | $this->document = new \DOMDocument('1.0', 'utf-8'); |
||
43 | $this->document->formatOutput = true; |
||
44 | |||
45 | $container = $this->document->createElementNS('http://symfony.com/schema/dic/services', 'container'); |
||
46 | $container->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); |
||
47 | $container->setAttribute('xsi:schemaLocation', 'http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd'); |
||
48 | |||
49 | $this->addParameters($container); |
||
50 | $this->addServices($container); |
||
51 | |||
52 | $this->document->appendChild($container); |
||
53 | $xml = $this->document->saveXML(); |
||
54 | unset($this->document); |
||
55 | |||
56 | return $this->container->resolveEnvPlaceholders($xml); |
||
57 | } |
||
58 | |||
59 | private function addParameters(\DOMElement $parent): void |
||
60 | { |
||
61 | $data = $this->container->getParameterBag()->all(); |
||
62 | if (!$data) { |
||
|
|||
63 | return; |
||
64 | } |
||
65 | |||
66 | if ($this->container->isCompiled()) { |
||
67 | $data = $this->escape($data); |
||
68 | } |
||
69 | |||
70 | $parameters = $this->document->createElement('parameters'); |
||
71 | $parent->appendChild($parameters); |
||
72 | $this->convertParameters($data, 'parameter', $parameters); |
||
73 | } |
||
74 | |||
75 | private function addMethodCalls(array $methodcalls, \DOMElement $parent): void |
||
76 | { |
||
77 | foreach ($methodcalls as $methodcall) { |
||
78 | $call = $this->document->createElement('call'); |
||
79 | $call->setAttribute('method', $methodcall[0]); |
||
80 | if (\count($methodcall[1])) { |
||
81 | $this->convertParameters($methodcall[1], 'argument', $call); |
||
82 | } |
||
83 | if ($methodcall[2] ?? false) { |
||
84 | $call->setAttribute('returns-clone', 'true'); |
||
85 | } |
||
86 | $parent->appendChild($call); |
||
87 | } |
||
88 | } |
||
89 | |||
90 | private function addService(Definition $definition, ?string $id, \DOMElement $parent): void |
||
91 | { |
||
92 | $service = $this->document->createElement('service'); |
||
93 | if (null !== $id) { |
||
94 | $service->setAttribute('id', $id); |
||
95 | } |
||
96 | if ($class = $definition->getClass()) { |
||
97 | if (str_starts_with($class, '\\')) { |
||
98 | $class = substr($class, 1); |
||
99 | } |
||
100 | |||
101 | $service->setAttribute('class', $class); |
||
102 | } |
||
103 | if (!$definition->isShared()) { |
||
104 | $service->setAttribute('shared', 'false'); |
||
105 | } |
||
106 | if ($definition->isPublic()) { |
||
107 | $service->setAttribute('public', 'true'); |
||
108 | } |
||
109 | if ($definition->isSynthetic()) { |
||
110 | $service->setAttribute('synthetic', 'true'); |
||
111 | } |
||
112 | if ($definition->isLazy()) { |
||
113 | $service->setAttribute('lazy', 'true'); |
||
114 | } |
||
115 | if (null !== $decoratedService = $definition->getDecoratedService()) { |
||
116 | [$decorated, $renamedId, $priority] = $decoratedService; |
||
117 | $service->setAttribute('decorates', $decorated); |
||
118 | |||
119 | $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; |
||
120 | if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) { |
||
121 | $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; |
||
122 | $service->setAttribute('decoration-on-invalid', $invalidBehavior); |
||
123 | } |
||
124 | if (null !== $renamedId) { |
||
125 | $service->setAttribute('decoration-inner-name', $renamedId); |
||
126 | } |
||
127 | if (0 !== $priority) { |
||
128 | $service->setAttribute('decoration-priority', $priority); |
||
129 | } |
||
130 | } |
||
131 | |||
132 | $tags = $definition->getTags(); |
||
133 | $tags['container.error'] = array_map(fn ($e) => ['message' => $e], $definition->getErrors()); |
||
134 | foreach ($tags as $name => $tags) { |
||
135 | foreach ($tags as $attributes) { |
||
136 | $tag = $this->document->createElement('tag'); |
||
137 | |||
138 | // Check if we have recursive attributes |
||
139 | if (array_filter($attributes, \is_array(...))) { |
||
140 | $tag->setAttribute('name', $name); |
||
141 | $this->addTagRecursiveAttributes($tag, $attributes); |
||
142 | } else { |
||
143 | if (!\array_key_exists('name', $attributes)) { |
||
144 | $tag->setAttribute('name', $name); |
||
145 | } else { |
||
146 | $tag->appendChild($this->document->createTextNode($name)); |
||
147 | } |
||
148 | foreach ($attributes as $key => $value) { |
||
149 | $tag->setAttribute($key, $value ?? ''); |
||
150 | } |
||
151 | } |
||
152 | $service->appendChild($tag); |
||
153 | } |
||
154 | } |
||
155 | |||
156 | if ($definition->getFile()) { |
||
157 | $file = $this->document->createElement('file'); |
||
158 | $file->appendChild($this->document->createTextNode($definition->getFile())); |
||
159 | $service->appendChild($file); |
||
160 | } |
||
161 | |||
162 | if ($parameters = $definition->getArguments()) { |
||
163 | $this->convertParameters($parameters, 'argument', $service); |
||
164 | } |
||
165 | |||
166 | if ($parameters = $definition->getProperties()) { |
||
167 | $this->convertParameters($parameters, 'property', $service, 'name'); |
||
168 | } |
||
169 | |||
170 | $this->addMethodCalls($definition->getMethodCalls(), $service); |
||
171 | |||
172 | if ($callable = $definition->getFactory()) { |
||
173 | if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { |
||
174 | $service->setAttribute('constructor', $callable[1]); |
||
175 | } else { |
||
176 | $factory = $this->document->createElement('factory'); |
||
177 | |||
178 | if (\is_array($callable) && $callable[0] instanceof Definition) { |
||
179 | $this->addService($callable[0], null, $factory); |
||
180 | $factory->setAttribute('method', $callable[1]); |
||
181 | } elseif (\is_array($callable)) { |
||
182 | if (null !== $callable[0]) { |
||
183 | $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); |
||
184 | } |
||
185 | $factory->setAttribute('method', $callable[1]); |
||
186 | } else { |
||
187 | $factory->setAttribute('function', $callable); |
||
188 | } |
||
189 | $service->appendChild($factory); |
||
190 | } |
||
191 | } |
||
192 | |||
193 | if ($definition->isDeprecated()) { |
||
194 | $deprecation = $definition->getDeprecation('%service_id%'); |
||
195 | $deprecated = $this->document->createElement('deprecated'); |
||
196 | $deprecated->appendChild($this->document->createTextNode($definition->getDeprecation('%service_id%')['message'])); |
||
197 | $deprecated->setAttribute('package', $deprecation['package']); |
||
198 | $deprecated->setAttribute('version', $deprecation['version']); |
||
199 | |||
200 | $service->appendChild($deprecated); |
||
201 | } |
||
202 | |||
203 | if ($definition->isAutowired()) { |
||
204 | $service->setAttribute('autowire', 'true'); |
||
205 | } |
||
206 | |||
207 | if ($definition->isAutoconfigured()) { |
||
208 | $service->setAttribute('autoconfigure', 'true'); |
||
209 | } |
||
210 | |||
211 | if ($definition->isAbstract()) { |
||
212 | $service->setAttribute('abstract', 'true'); |
||
213 | } |
||
214 | |||
215 | if ($callable = $definition->getConfigurator()) { |
||
216 | $configurator = $this->document->createElement('configurator'); |
||
217 | |||
218 | if (\is_array($callable) && $callable[0] instanceof Definition) { |
||
219 | $this->addService($callable[0], null, $configurator); |
||
220 | $configurator->setAttribute('method', $callable[1]); |
||
221 | } elseif (\is_array($callable)) { |
||
222 | $configurator->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); |
||
223 | $configurator->setAttribute('method', $callable[1]); |
||
224 | } else { |
||
225 | $configurator->setAttribute('function', $callable); |
||
226 | } |
||
227 | $service->appendChild($configurator); |
||
228 | } |
||
229 | |||
230 | $parent->appendChild($service); |
||
231 | } |
||
232 | |||
233 | private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent): void |
||
234 | { |
||
235 | $service = $this->document->createElement('service'); |
||
236 | $service->setAttribute('id', $alias); |
||
237 | $service->setAttribute('alias', $id); |
||
238 | if ($id->isPublic()) { |
||
239 | $service->setAttribute('public', 'true'); |
||
240 | } |
||
241 | |||
242 | if ($id->isDeprecated()) { |
||
243 | $deprecation = $id->getDeprecation('%alias_id%'); |
||
244 | $deprecated = $this->document->createElement('deprecated'); |
||
245 | $deprecated->appendChild($this->document->createTextNode($deprecation['message'])); |
||
246 | $deprecated->setAttribute('package', $deprecation['package']); |
||
247 | $deprecated->setAttribute('version', $deprecation['version']); |
||
248 | |||
249 | $service->appendChild($deprecated); |
||
250 | } |
||
251 | |||
252 | $parent->appendChild($service); |
||
253 | } |
||
254 | |||
255 | private function addServices(\DOMElement $parent): void |
||
256 | { |
||
257 | $definitions = $this->container->getDefinitions(); |
||
258 | if (!$definitions) { |
||
259 | return; |
||
260 | } |
||
261 | |||
262 | $services = $this->document->createElement('services'); |
||
263 | foreach ($definitions as $id => $definition) { |
||
264 | $this->addService($definition, $id, $services); |
||
265 | } |
||
266 | |||
267 | $aliases = $this->container->getAliases(); |
||
268 | foreach ($aliases as $alias => $id) { |
||
269 | while (isset($aliases[(string) $id])) { |
||
270 | $id = $aliases[(string) $id]; |
||
271 | } |
||
272 | $this->addServiceAlias($alias, $id, $services); |
||
273 | } |
||
274 | $parent->appendChild($services); |
||
275 | } |
||
276 | |||
277 | private function addTagRecursiveAttributes(\DOMElement $parent, array $attributes): void |
||
278 | { |
||
279 | foreach ($attributes as $name => $value) { |
||
280 | $attribute = $this->document->createElement('attribute'); |
||
281 | $attribute->setAttribute('name', $name); |
||
282 | |||
283 | if (\is_array($value)) { |
||
284 | $this->addTagRecursiveAttributes($attribute, $value); |
||
285 | } else { |
||
286 | $attribute->appendChild($this->document->createTextNode($value)); |
||
287 | } |
||
288 | |||
289 | $parent->appendChild($attribute); |
||
290 | } |
||
291 | } |
||
292 | |||
293 | private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key'): void |
||
386 | } |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * Escapes arguments. |
||
391 | */ |
||
392 | private function escape(array $arguments): array |
||
393 | { |
||
394 | $args = []; |
||
395 | foreach ($arguments as $k => $v) { |
||
396 | if (\is_array($v)) { |
||
397 | $args[$k] = $this->escape($v); |
||
398 | } elseif (\is_string($v)) { |
||
399 | $args[$k] = str_replace('%', '%%', $v); |
||
400 | } else { |
||
401 | $args[$k] = $v; |
||
402 | } |
||
403 | } |
||
404 | |||
405 | return $args; |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Converts php types to xml types. |
||
410 | * |
||
411 | * @throws RuntimeException When trying to dump object or resource |
||
412 | */ |
||
413 | public static function phpToXml(mixed $value): string |
||
430 | } |
||
431 | } |
||
432 | } |
||
433 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.