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
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.