Complex classes like Xml 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 Xml, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | class Xml extends AbstractFile |
||
29 | { |
||
30 | use LoggerTrait; |
||
31 | |||
32 | /** |
||
33 | * Kind. |
||
34 | */ |
||
35 | public const KIND = 'XmlEndpoint'; |
||
36 | |||
37 | /** |
||
38 | * XML root name. |
||
39 | * |
||
40 | * @var string |
||
41 | */ |
||
42 | protected $root_name = 'data'; |
||
43 | |||
44 | /** |
||
45 | * XMl node name. |
||
46 | * |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $node_name = 'row'; |
||
50 | |||
51 | /** |
||
52 | * Pretty output. |
||
53 | * |
||
54 | * @var bool |
||
55 | */ |
||
56 | protected $pretty = true; |
||
57 | |||
58 | /** |
||
59 | * Preserved whitespace. |
||
60 | * |
||
61 | * @var bool |
||
62 | */ |
||
63 | protected $preserve_whitespace = false; |
||
64 | |||
65 | /** |
||
66 | * Init endpoint. |
||
67 | */ |
||
68 | 22 | public function __construct(string $name, string $type, string $file, StorageInterface $storage, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = []) |
|
76 | |||
77 | /** |
||
78 | * {@inheritdoc} |
||
79 | */ |
||
80 | 11 | public function setup(bool $simulate = false): EndpointInterface |
|
81 | { |
||
82 | 11 | $streams = $this->storage->openReadStreams($this->file); |
|
83 | |||
84 | 11 | if ($this->type === EndpointInterface::TYPE_DESTINATION) { |
|
85 | 6 | $this->writable = $this->storage->openWriteStream($this->file); |
|
86 | } |
||
87 | |||
88 | 11 | foreach ($streams as $path => $stream) { |
|
89 | 11 | $dom = new DOMDocument('1.0', 'UTF-8'); |
|
90 | 11 | $dom->formatOutput = $this->pretty; |
|
91 | 11 | $dom->preserveWhiteSpace = $this->preserve_whitespace; |
|
92 | |||
93 | //read stream into memory since xml operates in-memory |
||
94 | 11 | $content = stream_get_contents($stream); |
|
95 | |||
96 | 11 | if ($this->type === EndpointInterface::TYPE_DESTINATION && empty($content)) { |
|
97 | $xml_root = $dom->createElement($this->root_name); |
||
98 | $xml_root = $dom->appendChild($xml_root); |
||
99 | } else { |
||
100 | 11 | $this->logger->debug('decode xml stream from ['.$path.']', [ |
|
101 | 11 | 'category' => get_class($this), |
|
102 | ]); |
||
103 | |||
104 | 11 | if ($dom->loadXML($content) === false) { |
|
105 | throw new XmlException\InvalidXml('could not decode xml stream from '.$path.''); |
||
106 | } |
||
107 | |||
108 | 11 | $xml_root = $dom->documentElement; |
|
109 | |||
110 | 11 | if (!$xml_root->hasChildNodes()) { |
|
111 | 5 | $level = $this->type === EndpointInterface::TYPE_SOURCE ? 'warning' : 'debug'; |
|
112 | |||
113 | 5 | $this->logger->$level('empty xml file ['.$path.'] given', [ |
|
114 | 5 | 'category' => get_class($this), |
|
115 | ]); |
||
116 | } |
||
117 | } |
||
118 | |||
119 | 11 | $this->files[] = [ |
|
120 | 11 | 'dom' => $dom, |
|
121 | 11 | 'xml_root' => $xml_root, |
|
122 | 11 | 'path' => $path, |
|
123 | 11 | 'stream' => $stream, |
|
124 | ]; |
||
125 | } |
||
126 | |||
127 | 11 | return $this; |
|
128 | } |
||
129 | |||
130 | /** |
||
131 | * Set options. |
||
132 | */ |
||
133 | 1 | public function setXmlOptions(?array $config = null): EndpointInterface |
|
134 | { |
||
135 | 1 | if ($config === null) { |
|
136 | return $this; |
||
137 | } |
||
138 | |||
139 | 1 | foreach ($config as $option => $value) { |
|
140 | switch ($option) { |
||
141 | 1 | case 'node_name': |
|
142 | 1 | case 'root_name': |
|
143 | 1 | case 'pretty': |
|
144 | 1 | case 'preserve_whitespace': |
|
145 | $this->{$option} = $value; |
||
146 | |||
147 | break; |
||
148 | default: |
||
149 | 1 | throw new InvalidArgumentException('unknown xml option '.$option.' given'); |
|
150 | } |
||
151 | } |
||
152 | |||
153 | return $this; |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * {@inheritdoc} |
||
158 | */ |
||
159 | 6 | public function shutdown(bool $simulate = false): EndpointInterface |
|
160 | { |
||
161 | 6 | foreach ($this->files as $resource) { |
|
162 | 5 | if ($simulate === false && $this->type === EndpointInterface::TYPE_DESTINATION) { |
|
163 | 5 | $this->flush($simulate); |
|
164 | 4 | if (fwrite($this->writable, $resource['dom']->saveXML()) === false) { |
|
165 | throw new Exception\WriteOperationFailed('failed create xml file '.$resource['path']); |
||
166 | } |
||
167 | |||
168 | 4 | $this->storage->syncWriteStream($this->writable, $resource['path']); |
|
169 | 4 | fclose($resource['stream']); |
|
170 | } else { |
||
171 | fclose($resource['stream']); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | 5 | $this->files = []; |
|
176 | |||
177 | 5 | return $this; |
|
178 | } |
||
179 | |||
180 | /** |
||
181 | * {@inheritdoc} |
||
182 | */ |
||
183 | 12 | public function transformQuery(?array $query = null) |
|
184 | { |
||
185 | 12 | if ($this->filter_all !== null && empty($query)) { |
|
186 | return '//*['.QueryTransformer::transform($this->getFilterAll()).']'; |
||
187 | } |
||
188 | 12 | if (!empty($query)) { |
|
189 | 12 | if ($this->filter_all === null) { |
|
190 | 10 | return '//*['.QueryTransformer::transform($query).']'; |
|
191 | } |
||
192 | |||
193 | 2 | return '//*['.QueryTransformer::transform([ |
|
194 | '$and' => [ |
||
195 | 2 | $this->getFilterAll(), |
|
196 | 2 | $query, |
|
197 | ], |
||
198 | 2 | ]).']'; |
|
199 | } |
||
200 | |||
201 | return '//'.$this->node_name; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * {@inheritdoc} |
||
206 | */ |
||
207 | public function getAll(?array $query = null): Generator |
||
208 | { |
||
209 | $filter = $this->transformQuery($query); |
||
210 | $i = 0; |
||
211 | $this->logGetAll($filter); |
||
212 | |||
213 | foreach ($this->files as $xml) { |
||
214 | $this->logger->debug('find xml nodes with xpath ['.$filter.'] in ['.$xml['path'].']', [ |
||
215 | 'category' => get_class($this), |
||
216 | ]); |
||
217 | |||
218 | $xpath = new \DOMXPath($xml['dom']); |
||
219 | $node = $xpath->query($filter); |
||
220 | |||
221 | foreach ($node as $result) { |
||
222 | $result = Converter::xmlToArray($result); |
||
223 | |||
224 | if (!is_array($result)) { |
||
225 | $this->logger->error('xml needs to yield objects ['.$xml['path'].'], make sure a propper endpoint filter has been set', [ |
||
226 | 'category' => get_class($this), |
||
227 | ]); |
||
228 | |||
229 | continue; |
||
230 | } |
||
231 | |||
232 | yield $this->build($result); |
||
233 | ++$i; |
||
234 | } |
||
235 | } |
||
236 | |||
237 | return $i; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * {@inheritdoc} |
||
242 | */ |
||
243 | 1 | public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string |
|
244 | { |
||
245 | 1 | $xml = $this->files[0]; |
|
246 | 1 | $current_track = $xml['dom']->createElement($this->node_name); |
|
247 | 1 | $current_track = $xml['xml_root']->appendChild($current_track); |
|
248 | |||
249 | 1 | foreach ($object as $column => $value) { |
|
250 | 1 | if (is_array($value)) { |
|
251 | $attr_subnode = $current_track->appendChild($xml['dom']->createElement($column)); |
||
252 | foreach ($value as $val) { |
||
253 | $attr_subnode->appendChild($xml['dom']->createElement($column, $val)); |
||
254 | } |
||
255 | } else { |
||
256 | 1 | $current_track->appendChild($xml['dom']->createElement($column, $value)); |
|
257 | } |
||
258 | } |
||
259 | |||
260 | 1 | $this->logCreate($object); |
|
261 | |||
262 | 1 | return null; |
|
263 | } |
||
264 | |||
265 | /** |
||
266 | * {@inheritdoc} |
||
267 | */ |
||
268 | 1 | public function getDiff(AttributeMapInterface $map, array $diff): array |
|
269 | { |
||
270 | 1 | return $diff; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * {@inheritdoc} |
||
275 | */ |
||
276 | 1 | public function change(AttributeMapInterface $map, array $diff, array $object, EndpointObjectInterface $endpoint_object, bool $simulate = false): ?string |
|
277 | { |
||
278 | 1 | $xml = $this->files[0]; |
|
279 | 1 | $attrs = []; |
|
|
|||
280 | 1 | $this->logChange($endpoint_object->getFilter(), $diff); |
|
281 | 1 | $xpath = new \DOMXPath($xml['dom']); |
|
282 | 1 | $node = $xpath->query($endpoint_object->getFilter()); |
|
283 | 1 | $node = $node[0]; |
|
284 | |||
285 | 1 | foreach ($diff as $attribute => $update) { |
|
286 | 1 | $child = $this->getChildNode($node, $attribute); |
|
287 | |||
288 | 1 | switch ($update['action']) { |
|
289 | 1 | case AttributeMapInterface::ACTION_REPLACE: |
|
290 | 1 | if (is_array($update['value'])) { |
|
291 | $new = $xml['dom']->createElement($attribute); |
||
292 | foreach ($update['value'] as $val) { |
||
293 | $new->appendChild($xml['dom']->createElement($attribute, $val)); |
||
294 | } |
||
295 | } else { |
||
296 | 1 | $new = $xml['dom']->createElement($attribute, $update['value']); |
|
297 | } |
||
298 | |||
299 | 1 | $node->replaceChild($new, $child); |
|
300 | |||
301 | 1 | break; |
|
302 | 1 | case AttributeMapInterface::ACTION_REMOVE: |
|
303 | 1 | $node->removeChild($child); |
|
304 | |||
305 | 1 | break; |
|
306 | 1 | case AttributeMapInterface::ACTION_ADD: |
|
307 | 1 | $node->appendChild($xml['dom']->createElement($attribute, $update['value'])); |
|
308 | |||
309 | 1 | break; |
|
310 | default: |
||
311 | 1 | throw new InvalidArgumentException('unknown action '.$update['action'].' given'); |
|
312 | } |
||
313 | } |
||
314 | |||
315 | 1 | return null; |
|
316 | } |
||
317 | |||
318 | /** |
||
319 | * {@inheritdoc} |
||
320 | */ |
||
321 | 1 | public function delete(AttributeMapInterface $map, array $object, EndpointObjectInterface $endpoint_object, bool $simulate = false): bool |
|
322 | { |
||
323 | 1 | $xml = $this->files[0]; |
|
324 | 1 | $this->logDelete($endpoint_object->getFilter()); |
|
325 | 1 | $xpath = new \DOMXPath($xml['dom']); |
|
326 | 1 | $node = $xpath->query($endpoint_object->getFilter()); |
|
327 | 1 | $node = $node[0]; |
|
328 | 1 | $xml['xml_root']->removeChild($node); |
|
329 | |||
330 | 1 | return true; |
|
331 | } |
||
332 | |||
333 | /** |
||
334 | * {@inheritdoc} |
||
335 | */ |
||
336 | 4 | public function getOne(array $object, array $attributes = []): EndpointObjectInterface |
|
363 | |||
364 | /** |
||
365 | * Get child node by name. |
||
366 | */ |
||
367 | 1 | protected function getChildNode(DOMNode $node, string $name) |
|
375 | } |
||
376 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.