Passed
Push — master ( 067151...1e051c )
by Petr
13:11
created

AbstractAgenda::setData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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