AbstractAgenda   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Test Coverage

Coverage 97.94%

Importance

Changes 0
Metric Value
wmc 45
eloc 91
c 0
b 0
f 0
dl 0
loc 325
ccs 95
cts 97
cp 0.9794
rs 8.8

13 Methods

Rating   Name   Duplication   Size   Complexity  
A namespace() 0 3 1
D addElements() 0 65 19
B addRefElement() 0 27 8
A getImportRoot() 0 3 1
A sanitize() 0 11 1
A appendNode() 0 12 2
A resolveOptions() 0 11 2
A setData() 0 6 2
A getChildKey() 0 6 3
A createXML() 0 6 1
A canImportRecursive() 0 3 1
A __construct() 0 7 1
A getChildNamespacePrefix() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like AbstractAgenda often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractAgenda, and based on these observations, apply Extract Interface, too.

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