Completed
Push — master ( b64fc1...5651bd )
by Raffael
16:20 queued 08:39
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 25
    public function __construct(string $name, string $type, string $file, StorageInterface $storage, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = [])
69
    {
70 25
        if (isset($resource['data']['resource'])) {
71 2
            $this->setXmlOptions($resource['data']['resource']);
72
        }
73
74 24
        parent::__construct($name, $type, $file, $storage, $collection, $workflow, $logger, $resource);
75 24
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 14
    public function setup(bool $simulate = false): EndpointInterface
81
    {
82 14
        if ($this->type === EndpointInterface::TYPE_DESTINATION) {
83 9
            $streams = [$this->file => $this->storage->openWriteStream($this->file)];
84
        } else {
85 5
            $streams = $this->storage->openReadStreams($this->file);
86
        }
87
88 14
        foreach ($streams as $path => $stream) {
89 14
            $dom = new DOMDocument('1.0', 'UTF-8');
90 14
            $dom->formatOutput = $this->pretty;
91 14
            $dom->preserveWhiteSpace = $this->preserve_whitespace;
92
93
            //read stream into memory since xml operates in-memory
94 14
            $content = stream_get_contents($stream);
95
96 14
            if ($this->type === EndpointInterface::TYPE_DESTINATION && empty($content)) {
97 4
                $xml_root = $dom->createElement($this->root_name);
98 4
                $xml_root = $dom->appendChild($xml_root);
99
            } else {
100 10
                $this->logger->debug('decode xml stream from ['.$path.']', [
101 10
                    'category' => get_class($this),
102
                ]);
103
104 10
                if ($dom->loadXML($content) === false) {
105
                    throw new XmlException\InvalidXml('could not decode xml stream from '.$path.'');
106
                }
107
108 10
                $xml_root = $dom->documentElement;
109
110 10
                if (!$xml_root->hasChildNodes()) {
111 4
                    $level = $this->type === EndpointInterface::TYPE_SOURCE ? 'warning' : 'debug';
112
113 4
                    $this->logger->$level('empty xml file ['.$path.'] given', [
114 4
                        'category' => get_class($this),
115
                    ]);
116
                }
117
            }
118
119 14
            $this->files[] = [
120 14
                'dom' => $dom,
121 14
                'xml_root' => $xml_root,
122 14
                'path' => $path,
123 14
                'stream' => $stream,
124
            ];
125
        }
126
127 14
        return $this;
128
    }
129
130
    /**
131
     * Set options.
132
     */
133 2
    public function setXmlOptions(?array $config = null): EndpointInterface
134
    {
135 2
        if ($config === null) {
136
            return $this;
137
        }
138
139 2
        foreach ($config as $option => $value) {
140 2
            switch ($option) {
141 2
                case 'node_name':
142 2
                case 'root_name':
143 2
                case 'pretty':
144 2
                case 'preserve_whitespace':
145 1
                    $this->{$option} = $value;
146
147 1
                    break;
148
                default:
149 2
                    throw new InvalidArgumentException('unknown xml option '.$option.' given');
150
            }
151
        }
152
153 1
        return $this;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 9
    public function shutdown(bool $simulate = false): EndpointInterface
160
    {
161 9
        foreach ($this->files as $resource) {
162 8
            if ($simulate === false && $this->type === EndpointInterface::TYPE_DESTINATION) {
163 8
                $this->flush($simulate);
164 7
                if (fwrite($resource['stream'], $resource['dom']->saveXML()) === false) {
165
                    throw new Exception\WriteOperationFailed('failed create xml file '.$resource['path']);
166
                }
167
168 7
                $this->storage->syncWriteStream($resource['stream'], $resource['path']);
169
            } else {
170 7
                fclose($resource['stream']);
171
            }
172
        }
173
174 8
        $this->files = [];
175
176 8
        return $this;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 14
    public function transformQuery(?array $query = null)
183
    {
184 14
        if ($this->filter_all !== null && empty($query)) {
185
            return '//*['.QueryTransformer::transform($this->getFilterAll()).']';
186
        }
187 14
        if (!empty($query)) {
188 14
            if ($this->filter_all === null) {
189 12
                return '//*['.QueryTransformer::transform($query).']';
190
            }
191
192 2
            return '//*['.QueryTransformer::transform([
193
                    '$and' => [
194 2
                        $this->getFilterAll(),
195 2
                        $query,
196
                    ],
197 2
                ]).']';
198
        }
199
200
        return '//*';
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function getAll(?array $query = null): Generator
207
    {
208
        $filter = $this->transformQuery($query);
209
        $i = 0;
210
        $this->logGetAll($filter);
211
212
        foreach ($this->files as $xml) {
213
            $this->logger->debug('find xml nodes with xpath ['.$filter.'] in ['.$xml['path'].']', [
214
                'category' => get_class($this),
215
            ]);
216
217
            $xpath = new \DOMXPath($xml['dom']);
218
            $node = $xpath->query($filter);
219
220
            foreach ($node as $result) {
221
                $result = Converter::xmlToArray($result);
222
                if (!is_array($result)) {
223
                    $this->logger->error('xml needs to yield objects ['.$xml['path'].'], make sure a propper endpoint filter has been set', [
224
                        'category' => get_class($this),
225
                    ]);
226
227
                    continue;
228
                }
229
230
                yield $this->build($result);
231
                ++$i;
232
            }
233
        }
234
235
        return $i;
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 4
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
242
    {
243 4
        $xml = $this->files[0];
244 4
        $current_track = $xml['dom']->createElement($this->node_name);
245 4
        $current_track = $xml['xml_root']->appendChild($current_track);
246
247 4
        foreach ($object as $column => $value) {
248 4
            if (is_array($value)) {
249 1
                $attr_subnode = $current_track->appendChild($xml['dom']->createElement($column));
250 1
                foreach ($value as $val) {
251 1
                    $attr_subnode->appendChild($xml['dom']->createElement($column, $val));
252
                }
253
            } else {
254 4
                $current_track->appendChild($xml['dom']->createElement($column, $value));
255
            }
256
        }
257
258 4
        $this->logCreate($object);
259
260 4
        return null;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266 1
    public function getDiff(AttributeMapInterface $map, array $diff): array
267
    {
268 1
        return $diff;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 1
    public function change(AttributeMapInterface $map, array $diff, array $object, array $endpoint_object, bool $simulate = false): ?string
275
    {
276 1
        $xml = $this->files[0];
277 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...
278 1
        $filter = $this->transformQuery($this->getFilterOne($object));
279 1
        $this->logChange($filter, $diff);
280 1
        $xpath = new \DOMXPath($xml['dom']);
281 1
        $node = $xpath->query($filter);
282 1
        $node = $node[0];
283
284 1
        foreach ($diff as $attribute => $update) {
285 1
            $child = $this->getChildNode($node, $attribute);
286
287 1
            switch ($update['action']) {
288 1
                case AttributeMapInterface::ACTION_REPLACE:
289 1
                    if (is_array($update['value'])) {
290
                        $new = $xml['dom']->createElement($attribute);
291
                        foreach ($update['value'] as $val) {
292
                            $new->appendChild($xml['dom']->createElement($attribute, $val));
293
                        }
294
                    } else {
295 1
                        $new = $xml['dom']->createElement($attribute, $update['value']);
296
                    }
297
298 1
                    $node->replaceChild($new, $child);
299
300 1
                break;
301 1
                case AttributeMapInterface::ACTION_REMOVE:
302 1
                    $node->removeChild($child);
303
304 1
                break;
305 1
                case AttributeMapInterface::ACTION_ADD:
306 1
                    $node->appendChild($xml['dom']->createElement($attribute, $update['value']));
307
308 1
                break;
309
                default:
310 1
                    throw new InvalidArgumentException('unknown action '.$update['action'].' given');
311
            }
312
        }
313
314 1
        return null;
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320 1
    public function delete(AttributeMapInterface $map, array $object, array $endpoint_object, bool $simulate = false): bool
321
    {
322 1
        $xml = $this->files[0];
323 1
        $filter = $this->transformQuery($this->getFilterOne($object));
324 1
        $this->logDelete($filter);
325 1
        $xpath = new \DOMXPath($xml['dom']);
326 1
        $node = $xpath->query($filter);
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);
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