ValidatorWorker   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Test Coverage

Coverage 70.83%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 76
c 2
b 0
f 0
dl 0
loc 164
ccs 34
cts 48
cp 0.7083
rs 10
wmc 9

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getSchemaPath() 0 29 3
A translateLibxmlErrors() 0 23 2
A validateSchema() 0 36 4
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\Package\Prime\Component\Xml\Contract\ValidatorWorkerInterface;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Exception\XmlException;
31
use LogicException;
32
33
/**
34
 * Clase para la validación de XML y manejo de errores.
35
 */
36
class ValidatorWorker extends AbstractWorker implements ValidatorWorkerInterface
37
{
38
    /**
39
     * Traducciones, y transformaciones, por defecto de los errores de libxml.
40
     *
41
     * El objetivo es simplificar los mensajes "técnicos" de libxml y dejarlos
42
     * más sencillos para que un humano "no técnico" los pueda entender más
43
     * fácilmente.
44
     *
45
     * @var array
46
     */
47
    private array $defaultLibxmlTranslations = [
48
        '\': '
49
            => '\' (línea %(line)s): ',
50
        ': [facet \'pattern\'] The value'
51
            => ': tiene el valor',
52
        ': This element is not expected. Expected is one of'
53
            => ': no era el esperado, el campo esperado era alguno de los siguientes',
54
        ': This element is not expected. Expected is'
55
            => ': no era el esperado, el campo esperado era',
56
        'is not accepted by the pattern'
57
            => 'el que no es válido según la expresión regular (patrón)',
58
        'is not a valid value of the local atomic type'
59
            => 'no es un valor válido para el tipo de dato del campo',
60
        'is not a valid value of the atomic type'
61
            => 'no es un valor válido, se requiere un valor de tipo',
62
        ': [facet \'maxLength\'] The value has a length of '
63
            => ': el valor del campo tiene un largo de ',
64
        '; this exceeds the allowed maximum length of '
65
            => ' caracteres excediendo el largo máximo permitido de ',
66
        ': [facet \'enumeration\'] The value '
67
            => ': el valor ',
68
        'is not an element of the set'
69
            => 'no es válido, debe ser alguno de los valores siguientes',
70
        '[facet \'minLength\'] The value has a length of'
71
            => 'el valor del campo tiene un largo de ',
72
        '; this underruns the allowed minimum length of'
73
            => ' y el largo mínimo requerido es',
74
        'Missing child element(s). Expected is'
75
            => 'debe tener en su interior, nivel inferior, el campo',
76
        'Character content other than whitespace is not allowed because the content type is \'element-only\''
77
            => 'el valor del campo es inválido',
78
        'Element'
79
            => 'Campo',
80
        ' ( '
81
            => ' \'',
82
        ' ).'
83
            => '\'.',
84
        'No matching global declaration available for the validation root'
85
            => 'El nodo raíz del XML no coincide con lo esperado en la definición del esquema',
86
    ];
87
88
    /**
89
     * {@inheritDoc}
90
     */
91 2
    public function validateSchema(
92
        XmlInterface $xml,
93
        ?string $schemaPath = null,
94
        array $translations = []
95
    ): void {
96
        // Determinar $schemaPath si no fue pasado.
97 2
        if ($schemaPath === null) {
98
            $schemaPath = $this->getSchemaPath($xml);
99
        }
100
101
        // Obtener estado actual de libxml y cambiarlo antes de validar para
102
        // poder obtenerlos a una variable si hay errores al validar.
103 2
        $useInternalErrors = libxml_use_internal_errors(true);
104
105
        // Validar el documento XML.
106 2
        $isValid = $xml->schemaValidate($schemaPath);
107
108
        // Obtener errores, limpiarlos y restaurar estado de errores de libxml.
109 2
        $errors = libxml_get_errors();
110 2
        libxml_clear_errors();
111 2
        libxml_use_internal_errors($useInternalErrors);
112
113
        // Si el XML no es válido lanzar excepción con los errores traducidos.
114 2
        if (!$isValid) {
115 1
            $errors = !empty($errors)
116 1
                ? $this->translateLibxmlErrors($errors, array_merge($translations, [
117 1
                    '{' . $xml->getNamespace() . '}' => '',
118 1
                ]))
119
                : []
120 1
            ;
121 1
            throw new XmlException(
122 1
                sprintf(
123 1
                    'La validación del XML falló usando el esquema %s.',
124 1
                    basename($schemaPath)
125 1
                ),
126 1
                $errors
127 1
            );
128
        }
129
    }
130
131
    /**
132
     * Traduce los errores de libxml a mensajes más sencillos para humanos.
133
     *
134
     * @param array $errors Arreglo con los errores originales de libxml.
135
     * @param array $translations Traducciones adicionales para aplicar.
136
     * @return array Arreglo con los errores traducidos.
137
     */
138 1
    private function translateLibxmlErrors(
139
        array $errors,
140
        array $translations = []
141
    ): array {
142
        // Definir reglas de traducción.
143 1
        $replace = array_merge($this->defaultLibxmlTranslations, $translations);
144
145
        // Traducir los errores.
146 1
        $translatedErrors = [];
147 1
        foreach ($errors as $error) {
148 1
            $translatedErrors[] = str_replace(
149 1
                ['%(line)s'],
150 1
                [(string) $error->line],
151 1
                str_replace(
152 1
                    array_keys($replace),
153 1
                    array_values($replace),
154 1
                    trim($error->message)
155 1
                )
156 1
            );
157
        }
158
159
        // Entregar errores traducidos.
160 1
        return $translatedErrors;
161
    }
162
163
    /**
164
     * Busca la ruta del esquema XML para validar el documento XML.
165
     *
166
     * @param XmlInterface $xml Documento XML para el cual se busca su
167
     * esquema XML.
168
     * @return string Ruta hacia el archivo XSD con el esquema del XML.
169
     * @throws XmlException Si el esquema del XML no se encuentra.
170
     */
171
    private function getSchemaPath(XmlInterface $xml): string
172
    {
173
        // Si no hay servicio de almacenamiento se debe dar un error.
174
        if (!isset($this->storageService)) {
175
            throw new LogicException(
176
                'No es posible determinar el esquema del XML para su validación pues no está asociado el servicio de almacenamiento. Se debe asociar el servicio o especificar la ruta absoluta al esquema al validar.'
177
            );
178
        }
179
180
        // Determinar el nombre del archivo del esquema del XML.
181
        $schema = $xml->getSchema();
182
        if ($schema === null) {
183
            throw new XmlException(
184
                'El XML no contiene una ubicación de esquema válida en el atributo "xsi:schemaLocation".'
185
            );
186
        }
187
188
        // Armar la ruta al archivo del esquema y corroborar que exista.
189
        $schemaPath = '';
190
        // $schemaPath = $this->storageService->getSchemasPath($schema);
191
        // if ($schemaPath === null) {
192
        //     throw new XmlException(sprintf(
193
        //         'No se encontró el archivo de esquema XML %s.',
194
        //         $schema
195
        //     ));
196
        // }
197
198
        // Entregar la ruta al esquema (existe y se puede leer el archivo).
199
        return $schemaPath;
200
    }
201
}
202