Passed
Push — master ( 3896ee...e50616 )
by Raffael
05:45
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.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 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
        if ($this->filter_all !== null) {
183 2
            $result = $this->filter_all;
184
        }
185
186 8
        if (!empty($query)) {
187 8
            if ($this->filter_all === null) {
188 6
                $result = '//*['.QueryTransformer::transform($query).']';
189
            } else {
190 2
                if (preg_match('#\[([^)]+)\]#', $this->filter_all, $match)) {
191 1
                    $new = '['.$match[1].' and '.QueryTransformer::transform($query).']';
192
193 1
                    return str_replace($match[0], $new, $this->filter_all);
194
                }
195
196 1
                $result = $this->filter_all.'['.QueryTransformer::transform($query).']';
197
            }
198
        }
199
200 7
        return $result;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function getAll(?array $query = null): Generator
207
    {
208
        $filter = $this->transformQuery($query);
209
        $i = 0;
210
211
        foreach ($this->files as $xml) {
212
            $this->logger->debug('find xml nodes with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
213
                'category' => get_class($this),
214
            ]);
215
216
            $xpath = new \DOMXPath($xml['dom']);
217
            $node = $xpath->query($filter);
218
219
            foreach ($node as $result) {
220
                $result = Converter::xmlToArray($result);
221
                yield $this->build($result);
222
                ++$i;
223
            }
224
        }
225
226
        return $i;
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232 4
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
233
    {
234 4
        $xml = $this->files[0];
235 4
        $current_track = $xml['dom']->createElement($this->node_name);
236 4
        $current_track = $xml['xml_root']->appendChild($current_track);
237
238 4
        foreach ($object as $column => $value) {
239 4
            if (is_array($value)) {
240 1
                $attr_subnode = $current_track->appendChild($xml['dom']->createElement($column));
241 1
                foreach ($value as $val) {
242 1
                    $attr_subnode->appendChild($xml['dom']->createElement($column, $val));
243
                }
244
            } else {
245 4
                $current_track->appendChild($xml['dom']->createElement($column, $value));
246
            }
247
        }
248
249 4
        $this->logger->debug('create new xml object on endpoint ['.$this->name.'] with values [{values}]', [
250 4
            'category' => get_class($this),
251 4
            'values' => $object,
252
        ]);
253
254 4
        return null;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 1
    public function getDiff(AttributeMapInterface $map, array $diff): array
261
    {
262 1
        return $diff;
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268 1
    public function change(AttributeMapInterface $map, array $diff, array $object, array $endpoint_object, bool $simulate = false): ?string
269
    {
270 1
        $xml = $this->files[0];
271 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...
272 1
        $filter = $this->getFilterOne($object);
273 1
        $xpath = new \DOMXPath($xml['dom']);
274 1
        $node = $xpath->query($filter);
275 1
        $node = $node[0];
276
277 1
        foreach ($diff as $attribute => $update) {
278 1
            $child = $this->getChildNode($node, $attribute);
279
280 1
            switch ($update['action']) {
281 1
                case AttributeMapInterface::ACTION_REPLACE:
282 1
                    if (is_array($update['value'])) {
283
                        $new = $xml['dom']->createElement($attribute);
284
                        foreach ($update['value'] as $val) {
285
                            $new->appendChild($xml['dom']->createElement($attribute, $val));
286
                        }
287
                    } else {
288 1
                        $new = $xml['dom']->createElement($attribute, $update['value']);
289
                    }
290
291 1
                    $node->replaceChild($new, $child);
292
293 1
                break;
294 1
                case AttributeMapInterface::ACTION_REMOVE:
295 1
                    $node->removeChild($child);
296
297 1
                break;
298 1
                case AttributeMapInterface::ACTION_ADD:
299 1
                    $node->appendChild($xml['dom']->createElement($attribute, $update['value']));
300
301 1
                break;
302
                default:
303 1
                    throw new InvalidArgumentException('unknown action '.$update['action'].' given');
304
            }
305
        }
306
307 1
        return null;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 1
    public function delete(AttributeMapInterface $map, array $object, array $endpoint_object, bool $simulate = false): bool
314
    {
315 1
        $xml = $this->files[0];
316 1
        $filter = $this->getFilterOne($object);
317 1
        $xpath = new \DOMXPath($xml['dom']);
318 1
        $node = $xpath->query($filter);
319 1
        $node = $node[0];
320 1
        $xml['xml_root']->removeChild($node);
321
322 1
        return true;
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     */
328 4
    public function getOne(array $object, array $attributes = []): EndpointObjectInterface
329
    {
330 4
        $filter = $this->getFilterOne($object);
331
332 4
        foreach ($this->files as $xml) {
333 4
            $this->logger->debug('find xml node with xpath ['.$filter.'] in ['.$xml['path'].'] on endpoint ['.$this->getIdentifier().']', [
334 4
                'category' => get_class($this),
335
            ]);
336
337 4
            $xpath = new \DOMXPath($xml['dom']);
338 4
            $nodes = $xpath->query($filter);
339
340 4
            $nodes = iterator_to_array($nodes);
341
342 4
            if (count($nodes) > 1) {
343 1
                throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
344
            }
345 3
            if (count($nodes) === 0) {
346 1
                throw new Exception\ObjectNotFound('no object found with filter '.$filter);
347
            }
348
349 2
            $node = Converter::xmlToArray(array_shift($nodes));
350
351 2
            return $this->build($node);
352
        }
353
    }
354
355
    /**
356
     * Get child node by name.
357
     */
358 1
    protected function getChildNode(DOMNode $node, string $name)
359
    {
360 1
        foreach ($node->childNodes as $child) {
361 1
            if ($child->nodeName === $name) {
362 1
                return $child;
363
            }
364
        }
365 1
    }
366
}
367