Passed
Push — master ( 221c35...fdb910 )
by Petr
02:59
created

AbstractAgenda::addRefElement()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 27
ccs 14
cts 14
cp 1
rs 8.4444
c 0
b 0
f 0
cc 8
nc 20
nop 4
crap 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace kalanis\Pohoda;
6
7
use InvalidArgumentException;
8
use ReflectionException;
9
use SimpleXMLElement;
10
11
/**
12
 * Base class for Pohoda objects.
13
 *
14
 * @method \void setNamespace(string $namespace)
15
 * @method \void setNodeName(string $nodeName)
16
 */
17
abstract class AbstractAgenda
18
{
19
    use Common\OneDirectionalVariablesTrait;
20
    use Common\ResolveOptionsTrait;
21
    use Common\DirectionAsResponseTrait;
22
23
    protected Common\Dtos\AbstractDto $data;
24
25
    /**
26
     * Construct agenda using provided data.
27
     *
28
     * @param DI\DependenciesFactory $dependenciesFactory
29
     */
30 194
    public function __construct(
31
        protected readonly DI\DependenciesFactory $dependenciesFactory,
32
    ) {
33 194
        $this->data = $this->getDefaultDto();
34
    }
35
36
    /**
37
     * Import root
38
     *
39
     * string for xml node, null for not existing one
40
     *
41
     * @return string|null
42
     */
43 3
    public function getImportRoot(): ?string
44
    {
45 3
        return null;
46
    }
47
48
    /**
49
     * Can read data recursively
50
     *
51
     * @return bool
52
     */
53 1
    public function canImportRecursive(): bool
54
    {
55 1
        return false;
56
    }
57
58
    /**
59
     * Set & resolve data options
60
     * Necessary for late setting when there is more options available
61
     *
62
     * @param Common\Dtos\AbstractDto $data
63
     *
64
     * @throws ReflectionException
65
     *
66
     * @return $this
67
     */
68 156
    public function setData(Common\Dtos\AbstractDto $data): self
69
    {
70
        // resolve options
71 156
        if ($this->resolveOptions) {
72 151
            $filteredData = Common\Dtos\Processing::filterUnusableData((array) $data);
73 151
            $resolvedData = $this->resolveOptions($filteredData);
74 151
            $this->data = Common\Dtos\Processing::hydrate($data, $resolvedData, $this->useOneDirectionalVariables);
75
        } else {
76 5
            $this->data = $data;
77
        }
78
79 156
        return $this;
80
    }
81
82
    /**
83
     * Get XML.
84
     *
85
     * @return SimpleXMLElement
86
     */
87
    abstract public function getXML(): SimpleXMLElement;
88
89
    /**
90
     * Configure options for options resolver.
91
     *
92
     * @param Common\OptionsResolver $resolver
93
     *
94
     * @return void
95
     */
96 151
    protected function configureOptions(Common\OptionsResolver $resolver): void
97
    {
98 151
        $resolver->setDefined($this->getDataElements(true));
99 151
        Common\OptionsResolver\Normalizers\NormalizerFactory::loadNormalizersFromDto($resolver, $this->data, $this->useOneDirectionalVariables);
100
    }
101
102
    /**
103
     * Create XML.
104
     *
105
     * @return SimpleXMLElement
106
     */
107 128
    protected function createXML(): SimpleXMLElement
108
    {
109 128
        $np = $this->dependenciesFactory->getNamespacePaths()->allNamespaces();
110 128
        return new SimpleXMLElement('<?xml version="1.0" encoding="' . $this->dependenciesFactory->getSanitizeEncoding()->getEncoding() . '"?><root ' . \implode(' ', \array_map(function ($k, $v) {
111 128
            return 'xmlns:' . $k . '="' . $v . '"';
112 128
        }, \array_keys($np), \array_values($np))) . '></root>');
113
    }
114
115
    /**
116
     * Get namespace.
117
     *
118
     * @param string $short
119
     *
120
     * @return string
121
     */
122 128
    protected function namespace(string $short): string
123
    {
124 128
        return $this->dependenciesFactory->getNamespacePaths()->namespace($short);
125
    }
126
127
    /**
128
     * Add batch elements.
129
     *
130
     * @param SimpleXMLElement $xml
131
     * @param string[]         $elements
132
     * @param string|null      $namespace
133
     *
134
     * @return void
135
     */
136 117
    protected function addElements(SimpleXMLElement $xml, array $elements, ?string $namespace = null): void
137
    {
138 117
        $refElements = Common\Dtos\Processing::getRefAttributes($this->data, $this->useOneDirectionalVariables);
139 117
        $elementsAttributesMapper = Common\Dtos\Processing::getAttributesExtendingElements($this->data, $this->useOneDirectionalVariables);
140 117
        foreach ($elements as $element) {
141 117
            $nodeKey = $this->getNodeKey($element);
142
143 117
            if (!isset($this->data->{$element})) {
144 117
                continue;
145
            }
146 98
            $subElement = $this->data->{$element};
147
148
            // ref element
149 98
            if (\in_array($nodeKey, $refElements)) {
150 58
                $this->addRefElement(
151 58
                    $xml,
152 58
                    ($namespace ? $namespace . ':' : '') . $nodeKey,
153 58
                    $subElement,
154 58
                    $namespace,
155 58
                );
156 58
                continue;
157
            }
158
159
            // element attribute
160 98
            if (isset($elementsAttributesMapper[$nodeKey])) {
161 12
                $attrs = $elementsAttributesMapper[$nodeKey];
162
163
                // get element
164 12
                $attrElement = $namespace ? $xml->children($namespace, true)->{$attrs->attrElement} : $xml->{$attrs->attrElement};
165
166 12
                $sanitized = $this->sanitize($subElement);
167 12
                $attrs->attrNamespace
168
                    ? $attrElement->addAttribute(
169
                        $attrs->attrNamespace . ':' . $attrs->attrName,
170
                        $sanitized,
171
                        $this->namespace($attrs->attrNamespace),
172
                    )
173 12
                    : $attrElement->addAttribute($attrs->attrName, $sanitized);
174
175 12
                continue;
176
            }
177
178
            // Agenda object
179 98
            if ($subElement instanceof self) {
180
                // set namespace
181 94
                if ($namespace && \method_exists($subElement, 'setNamespace')) {
182 59
                    $subElement->setNamespace($namespace);
183
                }
184
185
                // set node name
186 94
                if (\method_exists($subElement, 'setNodeName')) {
187 59
                    $subElement->setNodeName($nodeKey);
188
                }
189
190 94
                $this->appendNode(
191 94
                    $xml,
192 94
                    $subElement->getXML(),
193 94
                );
194
195 94
                continue;
196
            }
197
198
            // array of Agenda objects
199 95
            if (\is_array($subElement)) {
200 80
                if (empty($subElement)) {
201 76
                    continue;
202
                }
203
204 36
                $child = $namespace ? $xml->addChild($namespace . ':' . $nodeKey, '', $this->namespace($namespace)) : $xml->addChild($nodeKey);
205
206 36
                foreach ($subElement as $node) {
207 36
                    if (is_a($node, self::class)) {
208 36
                        $this->appendNode(
209 36
                            $child,
210 36
                            $node->getXML(),
211 36
                        );
212
                    }
213
                }
214
215 36
                continue;
216
            }
217
218 91
            $sanitized = $this->sanitize($subElement);
219 91
            $namespace ? $xml->addChild(
220 91
                $namespace . ':' . $nodeKey,
221 91
                $sanitized,
222 91
                $this->namespace($namespace),
223 91
            )
224
                : $xml->addChild($nodeKey, $sanitized);
225
        }
226
    }
227
228
    /**
229
     * Add ref element.
230
     *
231
     * @param SimpleXMLElement $xml
232
     * @param string           $name
233
     * @param mixed            $value
234
     * @param string|null      $namespace
235
     *
236
     * @return SimpleXMLElement
237
     */
238 66
    protected function addRefElement(SimpleXMLElement $xml, string $name, mixed $value, ?string $namespace = null): SimpleXMLElement
239
    {
240 66
        $node = $namespace ?
241 58
            $xml->addChild(
242 58
                $name,
243 58
                '',
244 58
                $this->namespace($namespace),
245 58
            )
246 16
            : $xml->addChild($name);
247
248 66
        if (!\is_array($value)) {
249 39
            $value = ['ids' => $value];
250
        }
251
252 66
        foreach ($value as $key => $value1) {
253 66
            if (\is_array($value1)) {
254 3
                if (array_is_list($value1)) {
255 1
                    foreach ($value1 as $value2) {
256 1
                        $node->addChild(
257 1
                            $namespace . ':' . $key,
258 1
                            $this->sanitize($value2),
259 1
                            $this->namespace(\strval($namespace)),
260 1
                        );
261
                    }
262
                } else {
263 2
                    $node = $node->addChild(
264 2
                        $namespace . ':' . $key,
265 2
                        '',
266 2
                        $this->namespace(\strval($namespace)),
267 2
                    );
268
269 3
                    foreach ($value1 as $key2 => $value2) {
270 2
                        $node->addChild(
271 2
                            'typ:' . $key2,
272 2
                            $this->sanitize($value2),
273 2
                            $this->namespace('typ'),
274 2
                        );
275
                    }
276
                }
277
            } else {
278 63
                $node->addChild(
279 63
                    'typ:' . $key,
280 63
                    $this->sanitize($value1),
281 63
                    $this->namespace('typ'),
282 63
                );
283
            }
284
        }
285
286 66
        return $node;
287
    }
288
289
    /**
290
     * Sanitize value to XML.
291
     *
292
     * @param mixed $value
293
     *
294
     * @return string
295
     */
296 95
    protected function sanitize(mixed $value): string
297
    {
298 95
        $sanitizeEncoding = $this->dependenciesFactory->getSanitizeEncoding();
299 95
        $sanitizeEncoding->listingWithEncoding();
300
301 95
        return \htmlspecialchars(
302 95
            \array_reduce(
303 95
                $sanitizeEncoding->getListing()->getTransformers(),
304 95
                function (string $value, ValueTransformer\ValueTransformerInterface $transformer): string {
305 5
                    return $transformer->transform($value);
306 95
                },
307 95
                \strval($value),
308 95
            ),
309 95
        );
310
    }
311
312
    /**
313
     * Append SimpleXMLElement to another SimpleXMLElement.
314
     *
315
     * @param SimpleXMLElement $xml
316
     * @param SimpleXMLElement $node
317
     *
318
     * @return void
319
     */
320 96
    protected function appendNode(SimpleXMLElement $xml, SimpleXMLElement $node): void
321
    {
322 96
        $dom = \dom_import_simplexml($xml);
323 96
        $dom2 = \dom_import_simplexml($node);
324
325 96
        if (!$dom->ownerDocument) {
326
            // @codeCoverageIgnoreStart
327
            throw new InvalidArgumentException('Invalid XML.');
328
        }
329
        // @codeCoverageIgnoreEnd
330
331 96
        $dom->appendChild($dom->ownerDocument->importNode($dom2, true));
332
    }
333
334
    /**
335
     * Resolve options.
336
     *
337
     * @param array<string,mixed> $data
338
     *
339
     * @return array<string,mixed>
340
     */
341 151
    protected function resolveOptions(array $data): array
342
    {
343 151
        $resolver = Common\SharedResolver::getResolver($this, $this->useOneDirectionalVariables, $data);
344 151
        $this->configureOptions($resolver);
345 151
        return $resolver->resolve($data);
346
    }
347
348
    /**
349
     * Change key in entry to different one in accordance with import root config
350
     *
351
     * @param string $defaultKey
352
     *
353
     * @return string
354
     */
355 46
    protected function getChildKey(string $defaultKey): string
356
    {
357 46
        if (!empty($this->getImportRoot()) && $this->directionAsResponse) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getImportRoot() targeting kalanis\Pohoda\AbstractAgenda::getImportRoot() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
358 1
            return $this->getImportRoot();
359
        }
360 45
        return $defaultKey;
361
    }
362
363
    /**
364
     * Change namespace prefix to different one in accordance with import root config
365
     *
366
     * @param string $defaultPrefix
367
     *
368
     * @return string
369
     */
370 46
    protected function getChildNamespacePrefix(string $defaultPrefix): string
371
    {
372 46
        if (!empty($this->getImportRoot()) && $this->directionAsResponse) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getImportRoot() targeting kalanis\Pohoda\AbstractAgenda::getImportRoot() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
373 1
            list($prefix, ) = explode(':', $this->getImportRoot());
374 1
            return $prefix;
375
        }
376 45
        return $defaultPrefix;
377
    }
378
379
    /**
380
     * Get elements - properties in data class
381
     *
382
     * @param bool $withAttributes
383
     *
384
     * @return string[]
385
     */
386 151
    protected function getDataElements(bool $withAttributes = false): array
387
    {
388 151
        return Common\Dtos\Processing::getProperties(
389 151
            $this->data,
390 151
            $withAttributes,
391 151
            $this->useOneDirectionalVariables,
392 151
        );
393
    }
394
395
    /**
396
     * Change key for specific cases
397
     *
398
     * @param string $key
399
     *
400
     * @return string
401
     */
402 116
    protected function getNodeKey(string $key): string
403
    {
404 116
        return $key;
405
    }
406
407
    /**
408
     * Get DTO which has defined elements for the agenda
409
     *
410
     * @return Common\Dtos\AbstractDto
411
     */
412
    abstract protected function getDefaultDto(): Common\Dtos\AbstractDto;
413
}
414