EncoderWorker::nodeAddChilds()   B
last analyzed

Complexity

Conditions 8
Paths 14

Size

Total Lines 55
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 14.0087

Importance

Changes 0
Metric Value
cc 8
eloc 28
c 0
b 0
f 0
nc 14
nop 5
dl 0
loc 55
ccs 18
cts 33
cp 0.5455
crap 14.0087
rs 8.4444

How to fix   Long Method   

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
declare(strict_types=1);
4
5
/**
6
 * Derafu: Biblioteca PHP (Núcleo).
7
 * Copyright (C) Derafu <https://www.derafu.org>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace Derafu\Lib\Core\Package\Prime\Component\Xml\Worker;
26
27
use Derafu\Lib\Core\Foundation\Abstract\AbstractWorker;
28
use Derafu\Lib\Core\Helper\Xml as XmlUtil;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\EncoderWorkerInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
31
use Derafu\Lib\Core\Package\Prime\Component\Xml\Entity\Xml;
32
use DOMElement;
33
use DOMNode;
34
use InvalidArgumentException;
35
36
/**
37
 * Clase que que crea un documento XML a partir de un arreglo PHP.
38
 */
39
class EncoderWorker extends AbstractWorker implements EncoderWorkerInterface
40
{
41
    /**
42
     * Reglas para convertir de arreglo de PHP a XML y viceversa.
43
     *
44
     * @var array
45
     */
46
    private array $rules = [
47
        // ¿Cómo se deben procesar los valores de los nodos?.
48
        'node_values' => [
49
            // Valores que hacen que el nodo no se genere (se omite).
50
            'skip_generation' => [null, false, []],
51
            // Valores que generan un nodo vacío.
52
            'generate_empty' => ['', true],
53
        ],
54
    ];
55
56
    /**
57
     * {@inheritDoc}
58
     */
59 35
    public function encode(
60
        array $data,
61
        ?array $namespace = null,
62
        ?DOMElement $parent = null,
63
        ?XmlInterface $doc = null
64
    ): XmlInterface {
65
        // Si no hay un documento XML completo (desde raíz, no vale un nodo),
66
        // entonces se crea, pues se necesitará para crear los futuros nodos.
67 35
        if ($doc === null) {
68 32
            $doc = new Xml();
69
        }
70
71
        // Si no hay un elemento padre, entonces se está pidiendo crear el
72
        // documento XML desde 0 (desde el nodo raíz).
73 35
        if ($parent === null) {
74 35
            $parent = $doc;
75
        }
76
77
        // Iterar el primer nivel del arreglo para encontrar los tags que se
78
        // deben agregar al documento XML.
79 35
        foreach ($data as $key => $value) {
80
81
            // Si el índice es '@attributes' entonces el valor de este índice
82
            // es un arreglo donde la llave es el nombre del atributo del tag
83
            // de $parent y el valor es el valor del atributo.
84 31
            if ($key === '@attributes') {
85
                // Solo se agregan atributos si el valor es un arreglo.
86 6
                if (is_array($value)) {
87
                    // En la primera iteración de recursividad se debe revisar
88
                    // que $parent sea DOMElement. Y solo en ese caso seguir.
89 6
                    if ($parent instanceof DOMElement) {
90 6
                        $this->nodeAddAttributes($parent, $value);
91
                    }
92
                }
93
            }
94
95
            // Si el índice es '@value' entonces se debe asignar directamente
96
            // el valor al nodo, pues es un escalar (no un arreglo con nodos
97
            // hijos). Este caso normalmente se usa cuando se crea un nodo que
98
            // debe tener valor y atributos.
99 31
            elseif ($key === '@value') {
100 3
                if (!$this->skipValue($value)) {
101 3
                    $parent->nodeValue = XmlUtil::sanitize($value);
102
                }
103
            }
104
105
            // Acá el índice es el nombre de un nodo. En este caso, el nodo es
106
            // un arreglo. Por lo que se procesará recursivamente para agregar
107
            // a este nodo los nodos hijos que están en el arreglo.
108 31
            elseif (is_array($value)) {
109
                // Solo se crea el nodo si tiene nodos hijos. El nodo no será
110
                // creado si se pasa un arreglo vacio (sin hijos).
111 31
                if (!empty($value)) {
112 31
                    $this->nodeAddChilds(
113 31
                        $doc,
114 31
                        $parent,
115 31
                        $key,
116 31
                        $value,
117 31
                        $namespace
118 31
                    );
119
                }
120
            }
121
122
            // El nodo es un escalar (no es un arreglo, no son nodos hijos).
123
            // Por lo que se crea el nodo y asigna el valor directamente.
124
            else {
125 25
                if (!$this->skipValue($value)) {
126 25
                    $this->nodeAddValue(
127 25
                        $doc,
128 25
                        $parent,
129 25
                        $key,
130 25
                        (string) $value,
131 25
                        $namespace
132 25
                    );
133
                }
134
            }
135
        }
136
137
        // Entregar el documento XML generado.
138 35
        return $doc;
139
    }
140
141
    /**
142
     * Agrega atributos a un nodo XML a partir de un arreglo.
143
     *
144
     * @param DOMElement $node Nodo al que se agregarán los atributos.
145
     * @param array $attributes Arreglo de atributos (clave => valor).
146
     * @return void
147
     * @throws InvalidArgumentException Si un valor de atributo es un arreglo.
148
     */
149 6
    private function nodeAddAttributes(DOMElement $node, array $attributes): void
150
    {
151 6
        foreach ($attributes as $attribute => $value) {
152
            // Si el valor del atributo es un arreglo no se puede asignar.
153 6
            if (is_array($value)) {
154
                throw new InvalidArgumentException(sprintf(
155
                    'El tipo de dato del valor ingresado para el atributo "%s" del nodo "%s" es incorrecto (no puede ser un arreglo). El valor es: %s',
156
                    $attribute,
157
                    $node->tagName,
158
                    json_encode($value)
159
                ));
160
            }
161
162
            // Asignar el valor del atributo solo si no se debe omitir según
163
            // el tipo valor que se quiera asignar.
164 6
            if (!$this->skipValue($value)) {
165 6
                $node->setAttribute($attribute, $value);
166
            }
167
        }
168
    }
169
170
    /**
171
     * Agrega nodos hijos a un nodo XML a partir de un arreglo.
172
     *
173
     * @param XmlInterface $doc Documento XML en el que se agregarán los nodos.
174
     * @param DOMNode $parent Nodo padre al que se agregarán los
175
     * nodos hijos.
176
     * @param string $tagName Nombre del tag del nodo hijo.
177
     * @param array $childs Arreglo de datos de los nodos hijos.
178
     * @param array|null $namespace Espacio de nombres para el XML (URI y
179
     * prefijo).
180
     * @return void
181
     * @throws InvalidArgumentException Si un nodo hijo no es un arreglo.
182
     */
183 31
    private function nodeAddChilds(
184
        XmlInterface $doc,
185
        DOMNode $parent,
186
        string $tagName,
187
        array $childs,
188
        ?array $namespace = null,
189
    ): void {
190 31
        $keys = array_keys($childs);
191 31
        if (!is_int($keys[0])) {
192 31
            $childs = [$childs];
193
        }
194 31
        foreach ($childs as $child) {
195
            // Omitir valores que deben ser saltados.
196 31
            if ($this->skipValue($child)) {
197
                continue;
198
            }
199
200
            // Si el hijo es un arreglo se crea un nodo para el hijo y se
201
            // agregan los elementos que están en el arreglo.
202 31
            if (is_array($child)) {
203
204
                // Si el arreglo no es asociativo (con nuevos nodos) error.
205 31
                if (isset($child[0])) {
206
                    throw new InvalidArgumentException(sprintf(
207
                        'El nodo "%s" permite incluir arreglos, pero deben ser arreglos con otros nodos. El valor actual es incorrecto: %s',
208
                        $tagName,
209
                        json_encode($child)
210
                    ));
211
                }
212
213
                // Agregar nodos hijos del nodo hijo (agregar
214
                // asociativo al nodo $tagName).
215 31
                $Node = $namespace
216
                    ? $doc->createElementNS(
217
                        $namespace[0],
218
                        $namespace[1] . ':' . $tagName
219
                    )
220 31
                    : $doc->createElement($tagName)
221 31
                ;
222 31
                $parent->appendChild($Node);
0 ignored issues
show
Bug introduced by
It seems like $Node can also be of type false; however, parameter $node of DOMNode::appendChild() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

222
                $parent->appendChild(/** @scrutinizer ignore-type */ $Node);
Loading history...
223 31
                $this->encode($child, $namespace, $Node, $doc);
0 ignored issues
show
Bug introduced by
It seems like $Node can also be of type false; however, parameter $parent of Derafu\Lib\Core\Package\...EncoderWorker::encode() does only seem to accept DOMElement|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

223
                $this->encode($child, $namespace, /** @scrutinizer ignore-type */ $Node, $doc);
Loading history...
224
            }
225
            // Si el hijo no es un arreglo, es simplemente un nodo duplicado
226
            // que se debe agregar en el mismo nivel que el nodo padre.
227
            else {
228 3
                $value = XmlUtil::sanitize((string) $child);
229 3
                $Node = $namespace
230
                    ? $doc->createElementNS(
231
                        $namespace[0],
232
                        $namespace[1] . ':' . $tagName,
233
                        $value
234
                    )
235 3
                    : $doc->createElement($tagName, $value)
236 3
                ;
237 3
                $parent->appendChild($Node);
238
            }
239
        }
240
    }
241
242
    /**
243
     * Agrega un nodo XML con un valor escalar a un nodo padre.
244
     *
245
     * @param XmlInterface $doc Documento XML en el que se agregarán los nodos.
246
     * @param DOMNode $parent Nodo padre al que se agregará el nodo hijo.
247
     * @param string $tagName Nombre del tag del nodo hijo.
248
     * @param string $value Valor del nodo hijo.
249
     * @param array|null $namespace Espacio de nombres para el XML (URI y
250
     * prefijo).
251
     * @return void
252
     */
253 25
    private function nodeAddValue(
254
        XmlInterface $doc,
255
        DOMNode $parent,
256
        string $tagName,
257
        string $value,
258
        ?array $namespace = null,
259
    ): void {
260 25
        $value = XmlUtil::sanitize($value);
261 25
        $Node = $namespace
262
            ? $doc->createElementNS(
263
                $namespace[0],
264
                $namespace[1] . ':' . $tagName,
265
                $value
266
            )
267 25
            : $doc->createElement($tagName, $value)
268 25
        ;
269 25
        $parent->appendChild($Node);
0 ignored issues
show
Bug introduced by
It seems like $Node can also be of type false; however, parameter $node of DOMNode::appendChild() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
        $parent->appendChild(/** @scrutinizer ignore-type */ $Node);
Loading history...
270
    }
271
272
    /**
273
     * Verifica si un valor debe omitirse al generar un nodo XML.
274
     *
275
     * @param mixed $value Valor a verificar.
276
     * @return bool `true` si el valor debe omitirse, `false` en caso contrario.
277
     */
278 31
    private function skipValue(mixed $value): bool
279
    {
280 31
        return in_array(
281 31
            $value,
282 31
            $this->rules['node_values']['skip_generation'],
283 31
            true
284 31
        );
285
    }
286
287
    /**
288
     * Verifica si un valor debe generar un nodo XML vacío.
289
     *
290
     * @param mixed $value Valor a verificar.
291
     * @return bool `true` si el valor debe generar un nodo vacío, `false` en
292
     * caso contrario.
293
     */
294
    // private function createWithEmptyValue(mixed $value): bool
295
    // {
296
    //     return in_array(
297
    //         $value,
298
    //         $this->rules['node_values']['generate_empty'],
299
    //         true
300
    //     );
301
    // }
302
}
303