AbstractAgenda::addElements()   D
last analyzed

Complexity

Conditions 19
Paths 18

Size

Total Lines 65
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 19.0672

Importance

Changes 0
Metric Value
eloc 33
c 0
b 0
f 0
dl 0
loc 65
ccs 33
cts 35
cp 0.9429
rs 4.5166
cc 19
nc 18
nop 3
crap 19.0672

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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