Passed
Branch master (0b4ab1)
by Esteban De La Fuente
74:02 queued 50:02
created

EncoderWorker::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 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\Entity\Xml;
31
use DOMElement;
32
use DOMNode;
33
use InvalidArgumentException;
34
35
/**
36
 * Clase que que crea un documento XML a partir de un arreglo PHP.
37
 */
38
class EncoderWorker extends AbstractWorker implements EncoderWorkerInterface
39
{
40
    /**
41
     * Reglas para convertir de arreglo de PHP a XML y viceversa.
42
     *
43
     * @var array
44
     */
45
    private array $rules = [
46
        // ¿Cómo se deben procesar los valores de los nodos?.
47
        'node_values' => [
48
            // Valores que hacen que el nodo no se genere (se omite).
49
            'skip_generation' => [null, false, []],
50
            // Valores que generan un nodo vacío.
51
            'generate_empty' => ['', true],
52
        ],
53
    ];
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 35
    public function encode(
59
        array $data,
60
        ?array $namespace = null,
61
        ?DOMElement $parent = null,
62
        ?Xml $doc = null
63
    ): Xml {
64
        // Si no hay un documento XML completo (desde raíz, no vale un nodo),
65
        // entonces se crea, pues se necesitará para crear los futuros nodos.
66 35
        if ($doc === null) {
67 32
            $doc = new Xml();
68
        }
69
70
        // Si no hay un elemento padre, entonces se está pidiendo crear el
71
        // documento XML desde 0 (desde el nodo raíz).
72 35
        if ($parent === null) {
73 35
            $parent = $doc;
74
        }
75
76
        // Iterar el primer nivel del arreglo para encontrar los tags que se
77
        // deben agregar al documento XML.
78 35
        foreach ($data as $key => $value) {
79
80
            // Si el índice es '@attributes' entonces el valor de este índice
81
            // es un arreglo donde la llave es el nombre del atributo del tag
82
            // de $parent y el valor es el valor del atributo.
83 31
            if ($key === '@attributes') {
84
                // Solo se agregan atributos si el valor es un arreglo.
85 6
                if (is_array($value)) {
86
                    // En la primera iteración de recursividad se debe revisar
87
                    // que $parent sea DOMElement. Y solo en ese caso seguir.
88 6
                    if ($parent instanceof DOMElement) {
89 6
                        $this->nodeAddAttributes($parent, $value);
90
                    }
91
                }
92
            }
93
94
            // Si el índice es '@value' entonces se debe asignar directamente
95
            // el valor al nodo, pues es un escalar (no un arreglo con nodos
96
            // hijos). Este caso normalmente se usa cuando se crea un nodo que
97
            // debe tener valor y atributos.
98 31
            elseif ($key === '@value') {
99 3
                if (!$this->skipValue($value)) {
100 3
                    $parent->nodeValue = XmlUtil::sanitize($value);
101
                }
102
            }
103
104
            // Acá el índice es el nombre de un nodo. En este caso, el nodo es
105
            // un arreglo. Por lo que se procesará recursivamente para agregar
106
            // a este nodo los nodos hijos que están en el arreglo.
107 31
            elseif (is_array($value)) {
108
                // Solo se crea el nodo si tiene nodos hijos. El nodo no será
109
                // creado si se pasa un arreglo vacio (sin hijos).
110 31
                if (!empty($value)) {
111 31
                    $this->nodeAddChilds(
112 31
                        $doc,
113 31
                        $parent,
114 31
                        $key,
115 31
                        $value,
116 31
                        $namespace
117 31
                    );
118
                }
119
            }
120
121
            // El nodo es un escalar (no es un arreglo, no son nodos hijos).
122
            // Por lo que se crea el nodo y asigna el valor directamente.
123
            else {
124 25
                if (!$this->skipValue($value)) {
125 25
                    $this->nodeAddValue(
126 25
                        $doc,
127 25
                        $parent,
128 25
                        $key,
129 25
                        (string) $value,
130 25
                        $namespace
131 25
                    );
132
                }
133
            }
134
        }
135
136
        // Entregar el documento XML generado.
137 35
        return $doc;
138
    }
139
140
    /**
141
     * Agrega atributos a un nodo XML a partir de un arreglo.
142
     *
143
     * @param DOMElement $node Nodo al que se agregarán los atributos.
144
     * @param array $attributes Arreglo de atributos (clave => valor).
145
     * @return void
146
     * @throws InvalidArgumentException Si un valor de atributo es un arreglo.
147
     */
148 6
    private function nodeAddAttributes(DOMElement $node, array $attributes): void
149
    {
150 6
        foreach ($attributes as $attribute => $value) {
151
            // Si el valor del atributo es un arreglo no se puede asignar.
152 6
            if (is_array($value)) {
153
                throw new InvalidArgumentException(sprintf(
154
                    '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',
155
                    $attribute,
156
                    $node->tagName,
157
                    json_encode($value)
158
                ));
159
            }
160
161
            // Asignar el valor del atributo solo si no se debe omitir según
162
            // el tipo valor que se quiera asignar.
163 6
            if (!$this->skipValue($value)) {
164 6
                $node->setAttribute($attribute, $value);
165
            }
166
        }
167
    }
168
169
    /**
170
     * Agrega nodos hijos a un nodo XML a partir de un arreglo.
171
     *
172
     * @param Xml $doc Documento XML en el que se agregarán los nodos.
173
     * @param DOMNode $parent Nodo padre al que se agregarán los
174
     * nodos hijos.
175
     * @param string $tagName Nombre del tag del nodo hijo.
176
     * @param array $childs Arreglo de datos de los nodos hijos.
177
     * @param array|null $namespace Espacio de nombres para el XML (URI y
178
     * prefijo).
179
     * @return void
180
     * @throws InvalidArgumentException Si un nodo hijo no es un arreglo.
181
     */
182 31
    private function nodeAddChilds(
183
        Xml $doc,
184
        DOMNode $parent,
185
        string $tagName,
186
        array $childs,
187
        ?array $namespace = null,
188
    ): void {
189 31
        $keys = array_keys($childs);
190 31
        if (!is_int($keys[0])) {
191 31
            $childs = [$childs];
192
        }
193 31
        foreach ($childs as $child) {
194
            // Omitir valores que deben ser saltados.
195 31
            if ($this->skipValue($child)) {
196
                continue;
197
            }
198
199
            // Si el hijo es un arreglo se crea un nodo para el hijo y se
200
            // agregan los elementos que están en el arreglo.
201 31
            if (is_array($child)) {
202
203
                // Si el arreglo no es asociativo (con nuevos nodos) error.
204 31
                if (isset($child[0])) {
205
                    throw new InvalidArgumentException(sprintf(
206
                        'El nodo "%s" permite incluir arreglos, pero deben ser arreglos con otros nodos. El valor actual es incorrecto: %s',
207
                        $tagName,
208
                        json_encode($child)
209
                    ));
210
                }
211
212
                // Agregar nodos hijos del nodo hijo (agregar
213
                // asociativo al nodo $tagName).
214 31
                $Node = $namespace
215
                    ? $doc->createElementNS(
216
                        $namespace[0],
217
                        $namespace[1] . ':' . $tagName
218
                    )
219 31
                    : $doc->createElement($tagName)
220 31
                ;
221 31
                $parent->appendChild($Node);
222 31
                $this->encode($child, $namespace, $Node, $doc);
223
            }
224
            // Si el hijo no es un arreglo, es simplemente un nodo duplicado
225
            // que se debe agregar en el mismo nivel que el nodo padre.
226
            else {
227 3
                $value = XmlUtil::sanitize((string) $child);
228 3
                $Node = $namespace
229
                    ? $doc->createElementNS(
230
                        $namespace[0],
231
                        $namespace[1] . ':' . $tagName,
232
                        $value
233
                    )
234 3
                    : $doc->createElement($tagName, $value)
235 3
                ;
236 3
                $parent->appendChild($Node);
237
            }
238
        }
239
    }
240
241
    /**
242
     * Agrega un nodo XML con un valor escalar a un nodo padre.
243
     *
244
     * @param Xml $doc Documento XML en el que se agregarán los nodos.
245
     * @param DOMNode $parent Nodo padre al que se agregará el nodo hijo.
246
     * @param string $tagName Nombre del tag del nodo hijo.
247
     * @param string $value Valor del nodo hijo.
248
     * @param array|null $namespace Espacio de nombres para el XML (URI y
249
     * prefijo).
250
     * @return void
251
     */
252 25
    private function nodeAddValue(
253
        Xml $doc,
254
        DOMNode $parent,
255
        string $tagName,
256
        string $value,
257
        ?array $namespace = null,
258
    ): void {
259 25
        $value = XmlUtil::sanitize($value);
260 25
        $Node = $namespace
261
            ? $doc->createElementNS(
262
                $namespace[0],
263
                $namespace[1] . ':' . $tagName,
264
                $value
265
            )
266 25
            : $doc->createElement($tagName, $value)
267 25
        ;
268 25
        $parent->appendChild($Node);
269
    }
270
271
    /**
272
     * Verifica si un valor debe omitirse al generar un nodo XML.
273
     *
274
     * @param mixed $value Valor a verificar.
275
     * @return bool `true` si el valor debe omitirse, `false` en caso contrario.
276
     */
277 31
    private function skipValue(mixed $value): bool
278
    {
279 31
        return in_array(
280 31
            $value,
281 31
            $this->rules['node_values']['skip_generation'],
282 31
            true
283 31
        );
284
    }
285
286
    /**
287
     * Verifica si un valor debe generar un nodo XML vacío.
288
     *
289
     * @param mixed $value Valor a verificar.
290
     * @return bool `true` si el valor debe generar un nodo vacío, `false` en
291
     * caso contrario.
292
     */
293
    // private function createWithEmptyValue(mixed $value): bool
294
    // {
295
    //     return in_array(
296
    //         $value,
297
    //         $this->rules['node_values']['generate_empty'],
298
    //         true
299
    //     );
300
    // }
301
}
302