Completed
Push — master ( 316baf...2178d1 )
by Raffael
67:25 queued 62:39
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-2019 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\Converter;
22
use Tubee\Endpoint\Xml\Exception as XmlException;
23
use Tubee\Endpoint\Xml\QueryTransformer;
24
use Tubee\EndpointObject\EndpointObjectInterface;
25
use Tubee\Storage\StorageInterface;
26
use Tubee\Workflow\Factory as WorkflowFactory;
27
28
class Xml extends AbstractFile
29
{
30
    /**
31
     * Kind.
32
     */
33
    public const KIND = 'XmlEndpoint';
34
35
    /**
36
     * XML root name.
37
     *
38
     * @var string
39
     */
40
    protected $root_name = 'data';
41
42
    /**
43
     * XMl node name.
44
     *
45
     * @var string
46
     */
47
    protected $node_name = 'row';
48
49
    /**
50
     * Pretty output.
51
     *
52
     * @var bool
53
     */
54
    protected $pretty = true;
55
56
    /**
57
     * Preserved whitespace.
58
     *
59
     * @var bool
60
     */
61
    protected $preserve_whitespace = false;
62
63
    /**
64
     * Init endpoint.
65
     */
66
    public function __construct(string $name, string $type, string $file, StorageInterface $storage, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = [])
67
    {
68
        if (isset($resource['data']['resource'])) {
69
            $this->setXmlOptions($resource['data']['resource']);
70
        }
71
72
        parent::__construct($name, $type, $file, $storage, $collection, $workflow, $logger, $resource);
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function setup(bool $simulate = false): EndpointInterface
79
    {
80
        if ($this->type === EndpointInterface::TYPE_DESTINATION) {
81
            $streams = [$this->file => $this->storage->openWriteStream($this->file)];
82
        } else {
83
            $streams = $this->storage->openReadStreams($this->file);
84
        }
85
86
        foreach ($streams as $path => $stream) {
87
            $dom = new DOMDocument('1.0', 'UTF-8');
88
            $dom->formatOutput = $this->pretty;
89
            $dom->preserveWhiteSpace = $this->preserve_whitespace;
90
91
            //read stream into memory since xml operates in-memory
92
            $content = stream_get_contents($stream);
93
94
            if ($this->type === EndpointInterface::TYPE_DESTINATION && empty($content)) {
95
                $xml_root = $dom->createElement($this->root_name);
96
                $xml_root = $dom->appendChild($xml_root);
97
            } else {
98
                $this->logger->debug('decode xml stream from ['.$path.']', [
99
                    'category' => get_class($this),
100
                ]);
101
102
                if ($dom->loadXML($content) === false) {
103
                    throw new XmlException\InvalidXml('could not decode xml stream from '.$path.'');
104
                }
105
106
                $xml_root = $dom->documentElement;
107
108
                if (!$xml_root->hasChildNodes()) {
109
                    $level = $this->type === EndpointInterface::TYPE_SOURCE ? 'warning' : 'debug';
110
111
                    $this->logger->$level('empty xml file ['.$path.'] given', [
112
                        'category' => get_class($this),
113
                    ]);
114
                }
115
            }
116
117
            $this->files[] = [
118
                'dom' => $dom,
119
                'xml_root' => $xml_root,
120
                'path' => $path,
121
                'stream' => $stream,
122
            ];
123
        }
124
125
        return $this;
126
    }
127
128
    /**
129
     * Set options.
130
     */
131
    public function setXmlOptions(?array $config = null): EndpointInterface
132
    {
133
        if ($config === null) {
134
            return $this;
135
        }
136
137
        foreach ($config as $option => $value) {
138
            switch ($option) {
139
                case 'node_name':
140
                case 'root_name':
141
                case 'pretty':
142
                case 'preserve_whitespace':
143
                    $this->{$option} = $value;
144
145
                    break;
146
                default:
147
                    throw new InvalidArgumentException('unknown xml option '.$option.' given');
148
            }
149
        }
150
151
        return $this;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function shutdown(bool $simulate = false): EndpointInterface
158
    {
159
        foreach ($this->files as $resource) {
160
            if ($simulate === false && $this->type === EndpointInterface::TYPE_DESTINATION) {
161
                $this->flush($simulate);
162
                if (fwrite($resource['stream'], $resource['dom']->saveXML()) === false) {
163
                    throw new Exception\WriteOperationFailed('failed create xml file '.$resource['path']);
164
                }
165
166
                $this->storage->syncWriteStream($resource['stream'], $resource['path']);
167
            }
168
169
            fclose($resource['stream']);
170
        }
171
172
        $this->files = [];
173
174
        return $this;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function transformQuery(?array $query = null)
181
    {
182
        $pre = '//'.$this->node_name;
183
        $result = $pre;
184
185
        if ($this->filter_all !== null) {
186
            $result = $pre.'['.$this->filter_all.']';
187
        }
188
189
        if (!empty($query)) {
190
            if ($this->filter_all === null) {
191
                $result = $pre.'['.QueryTransformer::transform($query).']';
192
            } else {
193
                $result = $pre.'['.$this->filter_all.' and '.QueryTransformer::transform($query).']';
194
            }
195
        }
196
197
        return $result;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function getAll(?array $query = null): Generator
204
    {
205
        $filter = $this->transformQuery($query);
206
        $i = 0;
207
208
        foreach ($this->files as $xml) {
209
            $this->logger->debug('find xml nodes with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
210
                'category' => get_class($this),
211
            ]);
212
213
            $xpath = new \DOMXPath($xml['dom']);
214
            $node = $xpath->query($filter);
215
216
            foreach ($node as $result) {
217
                $result = Converter::xmlToArray($result);
218
                yield $this->build($result);
219
                ++$i;
220
            }
221
        }
222
223
        return $i;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
230
    {
231
        $xml = $this->files[0];
232
        $current_track = $xml['dom']->createElement($this->node_name);
233
        $current_track = $xml['xml_root']->appendChild($current_track);
234
235
        foreach ($object as $column => $value) {
236
            if (is_array($value)) {
237
                $attr_subnode = $current_track->appendChild($xml['dom']->createElement($column));
238
                foreach ($value as $val) {
239
                    $attr_subnode->appendChild($xml['dom']->createElement($column, $val));
240
                }
241
            } else {
242
                $current_track->appendChild($xml['dom']->createElement($column, $value));
243
            }
244
        }
245
246
        $this->logger->debug('create new xml object on endpoint ['.$this->name.'] with values [{values}]', [
247
            'category' => get_class($this),
248
            'values' => $object,
249
        ]);
250
251
        return null;
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function getDiff(AttributeMapInterface $map, array $diff): array
258
    {
259
        return $diff;
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function change(AttributeMapInterface $map, array $diff, array $object, array $endpoint_object, bool $simulate = false): ?string
266
    {
267
        $xml = $this->files[0];
268
        $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...
269
        $filter = $this->getFilterOne($object);
270
        $xpath = new \DOMXPath($xml['dom']);
271
        $node = $xpath->query($filter);
272
        $node = $node[0];
273
274
        foreach ($diff as $attribute => $update) {
275
            $child = $this->getChildNode($node, $attribute);
276
277
            switch ($update['action']) {
278
                case AttributeMapInterface::ACTION_REPLACE:
279
                    if (is_array($update['value'])) {
280
                        $new = $xml['dom']->createElement($attribute);
281
                        foreach ($update['value'] as $val) {
282
                            $new->appendChild($xml['dom']->createElement($attribute, $val));
283
                        }
284
                    } else {
285
                        $new = $xml['dom']->createElement($attribute, $update['value']);
286
                    }
287
288
                    $node->replaceChild($new, $child);
289
290
                break;
291
                case AttributeMapInterface::ACTION_REMOVE:
292
                    $node->removeChild($child);
293
294
                break;
295
                case AttributeMapInterface::ACTION_ADD:
296
                    $child->appendChild($xml['dom']->createElement($attribute, $update['value']));
297
298
                break;
299
                default:
300
                    throw new InvalidArgumentException('unknown action '.$update['action'].' given');
301
            }
302
        }
303
304
        return null;
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310
    public function delete(AttributeMapInterface $map, array $object, array $endpoint_object, bool $simulate = false): bool
311
    {
312
        $xml = $this->files[0];
313
        $filter = $this->getFilterOne($object);
314
        $xpath = new \DOMXPath($xml['dom']);
315
        $node = $xpath->query($filter);
316
        $node = $node[0];
317
        $xml['xml_root']->removeChild($node);
318
319
        return true;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function getOne(array $object, array $attributes = []): EndpointObjectInterface
326
    {
327
        $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...
328
329
        foreach ($this->files as $xml) {
330
            $this->logger->debug('find xml node with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
331
                'category' => get_class($this),
332
            ]);
333
334
            $xpath = new \DOMXPath($xml['dom']);
335
            $nodes = $xpath->query($filter);
336
337
            $nodes = iterator_to_array($nodes);
338
339
            if (count($nodes) > 1) {
340
                throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
341
            }
342
            if (count($nodes) === 0) {
343
                throw new Exception\ObjectNotFound('no object found with filter '.$filter);
344
            }
345
346
            $node = Converter::xmlToArray(array_shift($nodes));
347
348
            return $this->build($node);
349
        }
350
    }
351
352
    /**
353
     * Get child node by name.
354
     */
355
    protected function getChildNode(DOMNode $node, string $name)
356
    {
357
        foreach ($node->childNodes as $child) {
358
            if ($child->nodeName === $name) {
359
                return $child;
360
            }
361
        }
362
    }
363
}
364