Passed
Push — master ( a08a48...8426a1 )
by Esteban De La Fuente
05:58
created

XmlEncoder::nodeAddChilds()   B

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 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 28
c 1
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
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
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
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Xml;
26
27
use DOMElement;
28
use DOMNode;
29
use InvalidArgumentException;
30
31
/**
32
 * Clase `XmlEncoder` que crea un documento XML a partir de un arreglo PHP.
33
 */
34
class XmlEncoder
35
{
36
    /**
37
     * Reglas para convertir de arreglo de PHP a XML y viceversa.
38
     *
39
     * @var array
40
     */
41
    private static array $rules = [
42
        // ¿Cómo se deben procesar los valores de los nodos?.
43
        'node_values' => [
44
            // Valores que hacen que el nodo no se genere (se omite).
45
            'skip_generation' => [null, false, []],
46
            // Valores que generan un nodo vacío.
47
            'generate_empty' => ['', true],
48
        ],
49
    ];
50
51
    /**
52
     * Convierte un arreglo PHP a un documento XML, generando los nodos y
53
     * respetando un espacio de nombres si se proporciona.
54
     *
55
     * @param array $data Arreglo con los datos que se usarán para generar XML.
56
     * @param array|null $namespace Espacio de nombres para el XML (URI y
57
     * prefijo).
58
     * @param DOMElement|null $parent Elemento padre para los nodos, o null
59
     * para que sea la raíz.
60
     * @param XmlDocument $doc El documento raíz del XML que se genera.
61
     * @return XmlDocument
62
     */
63 123
    public static function encode(
64
        array $data,
65
        ?array $namespace = null,
66
        ?DOMElement $parent = null,
67
        ?XmlDocument $doc = null
68
    ): XmlDocument {
69
        // Si no hay un documento XML completo (desde raíz, no vale un nodo),
70
        // entonces se crea, pues se necesitará para crear los futuros nodos.
71 123
        if ($doc === null) {
72 120
            $doc = new XmlDocument();
73
        }
74
75
        // Si no hay un elemento padre, entonces se está pidiendo crear el
76
        // documento XML desde 0 (desde el nodo raíz).
77 123
        if ($parent === null) {
78 123
            $parent = $doc;
79
        }
80
81
        // Iterar el primer nivel del arreglo para encontrar los tags que se
82
        // deben agregar al documento XML.
83 123
        foreach ($data as $key => $value) {
84
85
            // Si el índice es '@attributes' entonces el valor de este índice
86
            // es un arreglo donde la llave es el nombre del atributo del tag
87
            // de $parent y el valor es el valor del atributo.
88 119
            if ($key === '@attributes') {
89
                // Solo se agregan atributos si el valor es un arreglo.
90 96
                if (is_array($value)) {
91 96
                    self::nodeAddAttributes($parent, $value);
0 ignored issues
show
Bug introduced by
It seems like $parent can also be of type libredte\lib\Core\Xml\XmlDocument; however, parameter $node of libredte\lib\Core\Xml\Xm...er::nodeAddAttributes() does only seem to accept DOMElement, 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

91
                    self::nodeAddAttributes(/** @scrutinizer ignore-type */ $parent, $value);
Loading history...
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 119
            elseif ($key === '@value') {
100 65
                if (!self::skipValue($value)) {
101 65
                    $parent->nodeValue = XmlUtils::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 119
            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 119
                if (!empty($value)) {
112 119
                    self::nodeAddChilds($doc, $parent, $key, $value, $namespace);
113
                }
114
            }
115
116
            // El nodo es un escalar (no es un arreglo, no son nodos hijos).
117
            // Por lo que se crea el nodo y asigna el valor directamente.
118
            else {
119 113
                if (!self::skipValue($value)) {
120 113
                    self::nodeAddValue(
121 113
                        $doc,
122 113
                        $parent,
123 113
                        $key,
124 113
                        (string) $value,
125 113
                        $namespace
126 113
                    );
127
                }
128
            }
129
        }
130
131
        // Entregar el documento XML generado.
132 123
        return $doc;
133
    }
134
135
    /**
136
     * Agrega atributos a un nodo XML a partir de un arreglo.
137
     *
138
     * @param DOMElement $node Nodo al que se agregarán los atributos.
139
     * @param array $attributes Arreglo de atributos (clave => valor).
140
     * @return void
141
     * @throws InvalidArgumentException Si un valor de atributo es un arreglo.
142
     */
143 96
    private static function nodeAddAttributes(DOMElement $node, array $attributes): void
144
    {
145 96
        foreach ($attributes as $attribute => $value) {
146
            // Si el valor del atributo es un arreglo no se puede asignar.
147 96
            if (is_array($value)) {
148
                throw new InvalidArgumentException(sprintf(
149
                    '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',
150
                    $attribute,
151
                    $node->tagName,
152
                    json_encode($value)
153
                ));
154
            }
155
            // Asignar el valor del atributo solo si no se debe omitir según
156
            // el tipo valor que se quiera asignar.
157 96
            if (!self::skipValue($value)) {
158 96
                $node->setAttribute($attribute, $value);
159
            }
160
        }
161
    }
162
163
    /**
164
     * Agrega nodos hijos a un nodo XML a partir de un arreglo.
165
     *
166
     * @param XmlDocument $doc Documento XML en el que se agregarán los nodos.
167
     * @param DOMNode $parent Nodo padre al que se agregarán los
168
     * nodos hijos.
169
     * @param string $tagName Nombre del tag del nodo hijo.
170
     * @param array $childs Arreglo de datos de los nodos hijos.
171
     * @param array|null $namespace Espacio de nombres para el XML (URI y
172
     * prefijo).
173
     * @return void
174
     * @throws InvalidArgumentException Si un nodo hijo no es un arreglo.
175
     */
176 119
    private static function nodeAddChilds(
177
        XmlDocument $doc,
178
        DOMNode $parent,
179
        string $tagName,
180
        array $childs,
181
        ?array $namespace = null,
182
    ): void {
183 119
        $keys = array_keys($childs);
184 119
        if (!is_int($keys[0])) {
185 119
            $childs = [$childs];
186
        }
187 119
        foreach ($childs as $child) {
188
            // Omitir valores que deben ser saltados.
189 119
            if (self::skipValue($child)) {
190
                continue;
191
            }
192
193
            // Si el hijo es un arreglo se crea un nodo para el hijo y se
194
            // agregan los elementos que están en el arreglo.
195 119
            if (is_array($child)) {
196
197
                // Si el arreglo no es asociativo (con nuevos nodos) error.
198 119
                if (isset($child[0])) {
199
                    throw new InvalidArgumentException(sprintf(
200
                        'El nodo "%s" permite incluir arreglos, pero deben ser arreglos con otros nodos. El valor actual es incorrecto: %s',
201
                        $tagName,
202
                        json_encode($child)
203
                    ));
204
                }
205
206
                // Agregar nodos hijos del nodo hijo (agregar
207
                // asociativo al nodo $tagName).
208 119
                $Node = $namespace
209
                    ? $doc->createElementNS(
210
                        $namespace[0],
211
                        $namespace[1] . ':' . $tagName
212
                    )
213 119
                    : $doc->createElement($tagName)
214 119
                ;
215 119
                $parent->appendChild($Node);
216 119
                self::encode($child, $namespace, $Node, $doc);
217
            }
218
            // Si el hijo no es un arreglo, es simplemente un nodo duplicado
219
            // que se debe agregar en el mismo nivel que el nodo padre.
220
            else {
221 3
                $value = XmlUtils::sanitize((string) $child);
222 3
                $Node = $namespace
223
                    ? $doc->createElementNS(
224
                        $namespace[0],
225
                        $namespace[1] . ':' . $tagName,
226
                        $value
227
                    )
228 3
                    : $doc->createElement($tagName, $value)
229 3
                ;
230 3
                $parent->appendChild($Node);
231
            }
232
        }
233
    }
234
235
    /**
236
     * Agrega un nodo XML con un valor escalar a un nodo padre.
237
     *
238
     * @param XmlDocument $doc Documento XML en el que se agregarán los nodos.
239
     * @param DOMNode $parent Nodo padre al que se agregará el nodo hijo.
240
     * @param string $tagName Nombre del tag del nodo hijo.
241
     * @param string $value Valor del nodo hijo.
242
     * @param array|null $namespace Espacio de nombres para el XML (URI y
243
     * prefijo).
244
     * @return void
245
     */
246 113
    private static function nodeAddValue(
247
        XmlDocument $doc,
248
        DOMNode $parent,
249
        string $tagName,
250
        string $value,
251
        ?array $namespace = null,
252
    ): void {
253 113
        $value = XmlUtils::sanitize($value);
254 113
        $Node = $namespace
255
            ? $doc->createElementNS(
256
                $namespace[0],
257
                $namespace[1] . ':' . $tagName,
258
                $value
259
            )
260 113
            : $doc->createElement($tagName, $value)
261 113
        ;
262 113
        $parent->appendChild($Node);
263
    }
264
265
    /**
266
     * Verifica si un valor debe omitirse al generar un nodo XML.
267
     *
268
     * @param mixed $value Valor a verificar.
269
     * @return bool `true` si el valor debe omitirse, `false` en caso contrario.
270
     */
271 119
    private static function skipValue(mixed $value): bool
272
    {
273 119
        return in_array($value, self::$rules['node_values']['skip_generation'], true);
274
    }
275
276
    /**
277
     * Verifica si un valor debe generar un nodo XML vacío.
278
     *
279
     * @param mixed $value Valor a verificar.
280
     * @return bool `true` si el valor debe generar un nodo vacío, `false` en
281
     * caso contrario.
282
     */
283
    // private static function createWithEmptyValue(mixed $value): bool
284
    // {
285
    //     return in_array($value, self::$rules['node_values']['generate_empty'], true);
286
    // }
287
}
288