AbstractAgenda   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 35
eloc 78
dl 0
loc 275
ccs 82
cts 82
cp 1
rs 9.6
c 0
b 0
f 0

10 Methods

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