AbstractAgenda::resolveOptions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
c 0
b 0
f 0
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace kalanis\Pohoda;
6
7
use kalanis\PohodaException;
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
            $filteredData = Common\Dtos\Processing::remapEnumData($filteredData);
74 151
            $resolvedData = $this->resolveOptions($filteredData);
75 151
            $this->data = Common\Dtos\Processing::hydrate($data, $resolvedData, $this->useOneDirectionalVariables);
76
        } else {
77 5
            $this->data = $data;
78
        }
79
80 156
        return $this;
81
    }
82
83
    /**
84
     * Get XML.
85
     *
86
     * @return SimpleXMLElement
87
     */
88
    abstract public function getXML(): SimpleXMLElement;
89
90
    /**
91
     * Configure options for options resolver.
92
     *
93
     * @param Common\OptionsResolver $resolver
94
     *
95
     * @return void
96
     */
97 151
    protected function configureOptions(Common\OptionsResolver $resolver): void
98
    {
99 151
        $resolver->setDefined($this->getDataElements(true));
100 151
        Common\OptionsResolver\Normalizers\NormalizerFactory::loadNormalizersFromDto($resolver, $this->data, $this->useOneDirectionalVariables);
101
    }
102
103
    /**
104
     * Create XML.
105
     *
106
     * @return SimpleXMLElement
107
     */
108 128
    protected function createXML(): SimpleXMLElement
109
    {
110 128
        $np = $this->dependenciesFactory->getNamespacePaths()->allNamespaces();
111 128
        return new SimpleXMLElement('<?xml version="1.0" encoding="' . $this->dependenciesFactory->getSanitizeEncoding()->getEncoding() . '"?><root ' . \implode(' ', \array_map(function ($k, $v) {
112 128
            return 'xmlns:' . $k . '="' . $v . '"';
113 128
        }, \array_keys($np), \array_values($np))) . '></root>');
114
    }
115
116
    /**
117
     * Get namespace.
118
     *
119
     * @param string $short
120
     *
121
     * @return string
122
     */
123 128
    protected function namespace(string $short): string
124
    {
125 128
        return $this->dependenciesFactory->getNamespacePaths()->namespace($short);
126
    }
127
128
    /**
129
     * Add batch elements.
130
     *
131
     * @param SimpleXMLElement $xml
132
     * @param string[]         $elements
133
     * @param string|null      $namespace
134
     *
135
     * @return void
136
     */
137 117
    protected function addElements(SimpleXMLElement $xml, array $elements, ?string $namespace = null): void
138
    {
139 117
        $refElements = Common\Dtos\Processing::getRefAttributes($this->data, $this->useOneDirectionalVariables);
140 117
        $elementsAttributesMapper = Common\Dtos\Processing::getAttributesExtendingElements($this->data, $this->useOneDirectionalVariables);
141 117
        foreach ($elements as $element) {
142 117
            $nodeKey = $this->getNodeKey($element);
143
144 117
            if (!isset($this->data->{$element})) {
145 117
                continue;
146
            }
147 98
            $subElement = $this->data->{$element};
148
149
            // ref element
150 98
            if (\in_array($nodeKey, $refElements)) {
151 58
                $this->addRefElement(
152 58
                    $xml,
153 58
                    ($namespace ? $namespace . ':' : '') . $nodeKey,
154 58
                    $subElement,
155 58
                    $namespace,
156 58
                );
157 58
                continue;
158
            }
159
160
            // element attribute
161 98
            if (isset($elementsAttributesMapper[$nodeKey])) {
162 12
                $attrs = $elementsAttributesMapper[$nodeKey];
163
164
                // get element
165 12
                $attrElement = $namespace ? $xml->children($namespace, true)->{$attrs->attrElement} : $xml->{$attrs->attrElement};
166
167 12
                $sanitized = $this->sanitize($subElement);
168 12
                $attrs->attrNamespace
169
                    ? $attrElement->addAttribute(
170
                        $attrs->attrNamespace . ':' . $attrs->attrName,
171
                        $sanitized,
172
                        $this->namespace($attrs->attrNamespace),
173
                    )
174 12
                    : $attrElement->addAttribute($attrs->attrName, $sanitized);
175
176 12
                continue;
177
            }
178
179
            // Agenda object
180 98
            if ($subElement instanceof self) {
181
                // set namespace
182 94
                if ($namespace && \method_exists($subElement, 'setNamespace')) {
183 59
                    $subElement->setNamespace($namespace);
184
                }
185
186
                // set node name
187 94
                if (\method_exists($subElement, 'setNodeName')) {
188 59
                    $subElement->setNodeName($nodeKey);
189
                }
190
191 94
                $this->appendNode(
192 94
                    $xml,
193 94
                    $subElement->getXML(),
194 94
                );
195
196 94
                continue;
197
            }
198
199
            // array of Agenda objects
200 95
            if (\is_array($subElement)) {
201 80
                if (empty($subElement)) {
202 76
                    continue;
203
                }
204
205 36
                $child = $namespace ? $xml->addChild($namespace . ':' . $nodeKey, '', $this->namespace($namespace)) : $xml->addChild($nodeKey);
206
207 36
                foreach ($subElement as $node) {
208 36
                    if (is_a($node, self::class)) {
209 36
                        $this->appendNode(
210 36
                            $child,
211 36
                            $node->getXML(),
212 36
                        );
213
                    }
214
                }
215
216 36
                continue;
217
            }
218
219 91
            $sanitized = $this->sanitize($subElement);
220 91
            $namespace ? $xml->addChild(
221 91
                $namespace . ':' . $nodeKey,
222 91
                $sanitized,
223 91
                $this->namespace($namespace),
224 91
            )
225
                : $xml->addChild($nodeKey, $sanitized);
226
        }
227
    }
228
229
    /**
230
     * Add ref element.
231
     *
232
     * @param SimpleXMLElement $xml
233
     * @param string           $name
234
     * @param mixed            $value
235
     * @param string|null      $namespace
236
     *
237
     * @return SimpleXMLElement
238
     */
239 66
    protected function addRefElement(SimpleXMLElement $xml, string $name, mixed $value, ?string $namespace = null): SimpleXMLElement
240
    {
241 66
        $node = $namespace ?
242 58
            $xml->addChild(
243 58
                $name,
244 58
                '',
245 58
                $this->namespace($namespace),
246 58
            )
247 16
            : $xml->addChild($name);
248
249 66
        if (!\is_array($value)) {
250 39
            $value = ['ids' => $value];
251
        }
252
253 66
        foreach ($value as $key => $value1) {
254 66
            if (\is_array($value1)) {
255 3
                if (array_is_list($value1)) {
256 1
                    foreach ($value1 as $value2) {
257 1
                        $node->addChild(
258 1
                            $namespace . ':' . $key,
259 1
                            $this->sanitize($value2),
260 1
                            $this->namespace(\strval($namespace)),
261 1
                        );
262
                    }
263
                } else {
264 2
                    $node = $node->addChild(
265 2
                        $namespace . ':' . $key,
266 2
                        '',
267 2
                        $this->namespace(\strval($namespace)),
268 2
                    );
269
270 3
                    foreach ($value1 as $key2 => $value2) {
271 2
                        $node->addChild(
272 2
                            'typ:' . $key2,
273 2
                            $this->sanitize($value2),
274 2
                            $this->namespace('typ'),
275 2
                        );
276
                    }
277
                }
278
            } else {
279 63
                $node->addChild(
280 63
                    'typ:' . $key,
281 63
                    $this->sanitize($value1),
282 63
                    $this->namespace('typ'),
283 63
                );
284
            }
285
        }
286
287 66
        return $node;
288
    }
289
290
    /**
291
     * Sanitize value to XML.
292
     *
293
     * @param mixed $value
294
     *
295
     * @return string
296
     */
297 95
    protected function sanitize(mixed $value): string
298
    {
299 95
        $sanitizeEncoding = $this->dependenciesFactory->getSanitizeEncoding();
300 95
        $sanitizeEncoding->listingWithEncoding();
301
302 95
        return \htmlspecialchars(
303 95
            \array_reduce(
304 95
                $sanitizeEncoding->getListing()->getTransformers(),
305 95
                function (string $value, ValueTransformer\ValueTransformerInterface $transformer): string {
306 5
                    return $transformer->transform($value);
307 95
                },
308 95
                \strval($value),
309 95
            ),
310 95
        );
311
    }
312
313
    /**
314
     * Append SimpleXMLElement to another SimpleXMLElement.
315
     *
316
     * @param SimpleXMLElement $xml
317
     * @param SimpleXMLElement $node
318
     *
319
     * @return void
320
     */
321 96
    protected function appendNode(SimpleXMLElement $xml, SimpleXMLElement $node): void
322
    {
323 96
        $dom = \dom_import_simplexml($xml);
324 96
        $dom2 = \dom_import_simplexml($node);
325
326 96
        if (!$dom->ownerDocument) {
327
            // @codeCoverageIgnoreStart
328
            throw new PohodaException('Invalid XML.');
329
        }
330
        // @codeCoverageIgnoreEnd
331
332 96
        $dom->appendChild($dom->ownerDocument->importNode($dom2, true));
333
    }
334
335
    /**
336
     * Resolve options.
337
     *
338
     * @param array<string,mixed> $data
339
     *
340
     * @return array<string,mixed>
341
     */
342 151
    protected function resolveOptions(array $data): array
343
    {
344 151
        $resolver = Common\SharedResolver::getResolver($this, $this->useOneDirectionalVariables, $data);
345 151
        $this->configureOptions($resolver);
346 151
        return $resolver->resolve($data);
347
    }
348
349
    /**
350
     * Change key in entry to different one in accordance with import root config
351
     *
352
     * @param string $defaultKey
353
     *
354
     * @return string
355
     */
356 46
    protected function getChildKey(string $defaultKey): string
357
    {
358 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...
359 1
            return $this->getImportRoot();
360
        }
361 45
        return $defaultKey;
362
    }
363
364
    /**
365
     * Change namespace prefix to different one in accordance with import root config
366
     *
367
     * @param string $defaultPrefix
368
     *
369
     * @return string
370
     */
371 46
    protected function getChildNamespacePrefix(string $defaultPrefix): string
372
    {
373 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...
374 1
            list($prefix, ) = explode(':', $this->getImportRoot());
375 1
            return $prefix;
376
        }
377 45
        return $defaultPrefix;
378
    }
379
380
    /**
381
     * Get elements - properties in data class
382
     *
383
     * @param bool $withAttributes
384
     *
385
     * @return string[]
386
     */
387 151
    protected function getDataElements(bool $withAttributes = false): array
388
    {
389 151
        return Common\Dtos\Processing::getProperties(
390 151
            $this->data,
391 151
            $withAttributes,
392 151
            $this->useOneDirectionalVariables,
393 151
        );
394
    }
395
396
    /**
397
     * Change key for specific cases
398
     *
399
     * @param string $key
400
     *
401
     * @return string
402
     */
403 116
    protected function getNodeKey(string $key): string
404
    {
405 116
        return $key;
406
    }
407
408
    /**
409
     * Get DTO which has defined elements for the agenda
410
     *
411
     * @return Common\Dtos\AbstractDto
412
     */
413
    abstract protected function getDefaultDto(): Common\Dtos\AbstractDto;
414
}
415