Completed
Push — master ( ea9cfc...8dc8d9 )
by Raffael
12:43 queued 03:57
created

Xml::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 8
crap 6

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee.io
7
 *
8
 * @copyright   Copryright (c) 2017-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Tubee\Endpoint;
13
14
use DOMDocument;
15
use DOMNode;
16
use Generator;
17
use InvalidArgumentException;
18
use Psr\Log\LoggerInterface;
19
use Tubee\AttributeMap\AttributeMapInterface;
20
use Tubee\Collection\CollectionInterface;
21
use Tubee\Endpoint\Xml\Exception as XmlException;
22
use Tubee\Endpoint\Xml\QueryTransformer;
23
use Tubee\EndpointObject\EndpointObjectInterface;
24
use Tubee\Storage\StorageInterface;
25
use Tubee\Workflow\Factory as WorkflowFactory;
26
27
class Xml extends AbstractFile
28
{
29
    /**
30
     * Kind.
31
     */
32
    public const KIND = 'XmlEndpoint';
33
34
    /**
35
     * new XML element.
36
     *
37
     * @var SimpleXMLElement
38
     */
39
    protected $new_xml;
40
41
    /**
42
     * XML root name.
43
     *
44
     * @var string
45
     */
46
    protected $root_name = 'data';
47
48
    /**
49
     * XMl node name.
50
     *
51
     * @var string
52
     */
53
    protected $node_name = 'row';
54
55
    /**
56
     * Pretty output.
57
     *
58
     * @var bool
59
     */
60
    protected $pretty = true;
61
62
    /**
63
     * Preserved whitespace.
64
     *
65
     * @var bool
66
     */
67
    protected $preserve_whitespace = false;
68
69
    /**
70
     * Init endpoint.
71
     */
72
    public function __construct(string $name, string $type, string $file, StorageInterface $storage, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = [])
73
    {
74
        if (isset($resource['data']['resource'])) {
75
            $this->setXmlOptions($resource['data']['resource']);
76
        }
77
78
        parent::__construct($name, $type, $file, $storage, $collection, $workflow, $logger, $resource);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function setup(bool $simulate = false): EndpointInterface
85
    {
86
        if ($this->type === EndpointInterface::TYPE_DESTINATION) {
87
            $streams = [$this->file => $this->storage->openWriteStream($this->file)];
88
        } else {
89
            $streams = $this->storage->openReadStreams($this->file);
90
        }
91
92
        foreach ($streams as $path => $stream) {
93
            $dom = new DOMDocument('1.0', 'UTF-8');
94
            $dom->formatOutput = $this->pretty;
95
            $dom->preserveWhiteSpace = $this->preserve_whitespace;
96
97
            //read stream into memory since xml operates in-memory
98
            $content = stream_get_contents($stream);
99
100
            if ($this->type === EndpointInterface::TYPE_DESTINATION && empty($content)) {
101
                $xml_root = $dom->createElement($this->root_name);
102
                $xml_root = $dom->appendChild($xml_root);
103
            } else {
104
                $this->logger->debug('decode xml stream from ['.$path.']', [
105
                    'category' => get_class($this),
106
                ]);
107
108
                if ($dom->loadXML($content) === false) {
109
                    throw new XmlException\InvalidXml('could not decode xml stream from '.$path.'');
110
                }
111
112
                $xml_root = $dom->documentElement;
113
114
                if (!$xml_root->hasChildNodes()) {
115
                    $level = $this->type === EndpointInterface::TYPE_SOURCE ? 'warning' : 'debug';
116
117
                    $this->logger->$level('empty xml file ['.$path.'] given', [
118
                        'category' => get_class($this),
119
                    ]);
120
                }
121
            }
122
123
            $this->files[] = [
124
                'dom' => $dom,
125
                'xml_root' => $xml_root,
126
                'path' => $path,
127
                'stream' => $stream,
128
            ];
129
        }
130
131
        return $this;
132
    }
133
134
    /**
135
     * Set options.
136
     */
137
    public function setXmlOptions(?array $config = null): EndpointInterface
138
    {
139
        if ($config === null) {
140
            return $this;
141
        }
142
143
        foreach ($config as $option => $value) {
144
            switch ($option) {
145
                case 'node_name':
146
                case 'root_name':
147
                case 'pretty':
148
                case 'preserve_whitespace':
149
                    $this->{$option} = $value;
150
151
                    break;
152
                default:
153
                    throw new InvalidArgumentException('unknown xml option '.$option.' given');
154
            }
155
        }
156
157
        return $this;
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function shutdown(bool $simulate = false): EndpointInterface
164
    {
165
        foreach ($this->files as $resource) {
166
            if ($simulate === false && $this->type === EndpointInterface::TYPE_DESTINATION) {
167
                $this->flush($simulate);
168
                if (fwrite($resource['stream'], $resource['dom']->saveXML()) === false) {
169
                    throw new Exception\WriteOperationFailed('failed create xml file '.$resource['path']);
170
                }
171
172
                $this->storage->syncWriteStream($resource['stream'], $resource['path']);
173
            }
174
175
            fclose($resource['stream']);
176
        }
177
178
        $this->files = [];
179
180
        return $this;
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function transformQuery(?array $query = null)
187
    {
188
        $pre = '//'.$this->node_name;
189
        $result = $pre;
190
191
        if ($this->filter_all !== null) {
192
            $result = $pre.'['.$this->filter_all.']';
193
        }
194
195
        if (!empty($query)) {
196
            if ($this->filter_all === null) {
197
                $result = $pre.'['.QueryTransformer::transform($query).']';
198
            } else {
199
                $result = $pre.'['.$this->filter_all.' and '.QueryTransformer::transform($query).']';
200
            }
201
        }
202
203
        return $result;
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function getAll(?array $query = null): Generator
210
    {
211
        $filter = $this->transformQuery($query);
212
        $i = 0;
213
214
        foreach ($this->files as $xml) {
215
            $this->logger->debug('find xml nodes with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
216
                'category' => get_class($this),
217
            ]);
218
219
            $xpath = new \DOMXPath($xml['dom']);
220
            $node = $xpath->query($filter);
221
222
            foreach ($node as $result) {
223
                $result = $this->xmlToArray($result);
224
                yield $this->build($result);
225
                ++$i;
226
            }
227
        }
228
229
        return $i;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
236
    {
237
        $xml = $this->files[0];
238
        $current_track = $xml['dom']->createElement($this->node_name);
239
        $current_track = $xml['xml_root']->appendChild($current_track);
240
241
        foreach ($object as $column => $value) {
242
            if (is_array($value)) {
243
                $attr_subnode = $current_track->appendChild($xml['dom']->createElement($column));
244
                foreach ($value as $val) {
245
                    $attr_subnode->appendChild($xml['dom']->createElement($column, $val));
246
                }
247
            } else {
248
                $current_track->appendChild($xml['dom']->createElement($column, $value));
249
            }
250
        }
251
252
        $this->logger->debug('create new xml object on endpoint ['.$this->name.'] with values [{values}]', [
253
            'category' => get_class($this),
254
            'values' => $object,
255
        ]);
256
257
        return null;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function getDiff(AttributeMapInterface $map, array $diff): array
264
    {
265
        return $diff;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function change(AttributeMapInterface $map, array $diff, array $object, array $endpoint_object, bool $simulate = false): ?string
272
    {
273
        $xml = $this->files[0];
274
        $attrs = [];
0 ignored issues
show
Unused Code introduced by
$attrs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

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.

Loading history...
275
        $filter = $this->getFilterOne($object);
276
        $xpath = new \DOMXPath($xml['dom']);
277
        $node = $xpath->query($filter);
278
        $node = $node[0];
279
280
        foreach ($diff as $attribute => $update) {
281
            $child = $this->getChildNode($node, $attribute);
282
283
            switch ($update['action']) {
284
                case AttributeMapInterface::ACTION_REPLACE:
285
                    if (is_array($update['value'])) {
286
                        $new = $xml['dom']->createElement($attribute);
287
                        foreach ($update['value'] as $val) {
288
                            $new->appendChild($xml['dom']->createElement($attribute, $val));
289
                        }
290
                    } else {
291
                        $new = $xml['dom']->createElement($attribute, $update['value']);
292
                    }
293
294
                    $node->replaceChild($new, $child);
295
296
                break;
297
                case AttributeMapInterface::ACTION_REMOVE:
298
                    $node->removeChild($child);
299
300
                break;
301
                case AttributeMapInterface::ACTION_ADD:
302
                    $child->appendChild($xml['dom']->createElement($attribute, $update['value']));
303
304
                break;
305
                default:
306
                    throw new InvalidArgumentException('unknown action '.$update['action'].' given');
307
            }
308
        }
309
310
        return null;
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    public function delete(AttributeMapInterface $map, array $object, array $endpoint_object, bool $simulate = false): bool
317
    {
318
        $xml = $this->files[0];
319
        $filter = $this->getFilterOne($object);
320
        $xpath = new \DOMXPath($xml['dom']);
321
        $node = $xpath->query($filter);
322
        $node = $node[0];
323
        $xml['xml_root']->removeChild($node);
324
325
        return true;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function getOne(array $object, array $attributes = []): EndpointObjectInterface
332
    {
333
        $filter = $pre = '//'.$this->node_name.'['.$this->getFilterOne($object).']';
0 ignored issues
show
Unused Code introduced by
$pre is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

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.

Loading history...
334
335
        foreach ($this->files as $xml) {
336
            $this->logger->debug('find xml node with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
337
                'category' => get_class($this),
338
            ]);
339
340
            $xpath = new \DOMXPath($xml['dom']);
341
            $nodes = $xpath->query($filter);
342
343
            $nodes = iterator_to_array($nodes);
344
345
            if (count($nodes) > 1) {
346
                throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
347
            }
348
            if (count($nodes) === 0) {
349
                throw new Exception\ObjectNotFound('no object found with filter '.$filter);
350
            }
351
352
            $node = $this->xmlToArray(array_shift($nodes));
353
354
            return $this->build($node);
355
        }
356
    }
357
358
    /**
359
     * Convert XMLElement into nicly formatted php array.
360
     */
361
    protected function xmlToArray($root)
362
    {
363
        $result = [];
364
365
        if ($root->hasAttributes()) {
366
            $attrs = $root->attributes;
367
            foreach ($attrs as $attr) {
368
                $result['@attributes'][$attr->name] = $attr->value;
369
            }
370
        }
371
372
        if ($root->hasChildNodes()) {
373
            $children = $root->childNodes;
374
375
            if ($children->length == 1) {
376
                $child = $children->item(0);
377
378
                if ($child->nodeType === XML_TEXT_NODE || $child->nodeType === XML_CDATA_SECTION_NODE) {
379
                    $result['_value'] = $child->nodeValue;
380
381
                    return count($result) == 1 ? $result['_value'] : $result;
382
                }
383
            }
384
385
            $groups = [];
386
            foreach ($children as $child) {
387
                if (!isset($result[$child->nodeName])) {
388
                    $result[$child->nodeName] = $this->xmlToArray($child);
389
                } else {
390
                    if (!isset($groups[$child->nodeName])) {
391
                        $result[$child->nodeName] = [$result[$child->nodeName]];
392
                        $groups[$child->nodeName] = 1;
393
                    }
394
                    $result[$child->nodeName][] = $this->xmlToArray($child);
395
                }
396
            }
397
        }
398
399
        return $result;
400
    }
401
402
    /**
403
     * Get child node by name.
404
     */
405
    protected function getChildNode(DOMNode $node, string $name)
406
    {
407
        foreach ($node->childNodes as $child) {
408
            if ($child->nodeName === $name) {
409
                return $child;
410
            }
411
        }
412
    }
413
}
414