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