Completed
Push — master ( 519f76...318dd5 )
by Raffael
24:13 queued 15:29
created

Xml::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

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
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
    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 = [])
69
    {
70 22
        if (isset($resource['data']['resource'])) {
71 1
            $this->setXmlOptions($resource['data']['resource']);
72
        }
73
74 21
        parent::__construct($name, $type, $file, $storage, $collection, $workflow, $logger, $resource);
75 21
    }
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 = [];
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...
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
337
    {
338 4
        $filter = $this->transformQuery($this->getFilterOne($object));
339 4
        $this->logGetOne($filter);
340
341 4
        foreach ($this->files as $xml) {
342 4
            $this->logger->debug('find xml node with xpath ['.$filter.'] in ['.$xml['path'].']', [
343 4
                'category' => get_class($this),
344
            ]);
345
346 4
            $xpath = new \DOMXPath($xml['dom']);
347 4
            $nodes = $xpath->query($filter);
348
349 4
            $nodes = iterator_to_array($nodes);
350
351 4
            if (count($nodes) > 1) {
352 1
                throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
353
            }
354 3
            if (count($nodes) === 0) {
355 1
                throw new Exception\ObjectNotFound('no object found with filter '.$filter);
356
            }
357
358 2
            $node = Converter::xmlToArray(array_shift($nodes));
359
360 2
            return $this->build($node, $filter);
361
        }
362
    }
363
364
    /**
365
     * Get child node by name.
366
     */
367 1
    protected function getChildNode(DOMNode $node, string $name)
368
    {
369 1
        foreach ($node->childNodes as $child) {
370 1
            if ($child->nodeName === $name) {
371 1
                return $child;
372
            }
373
        }
374 1
    }
375
}
376