Passed
Push — master ( 9d79d3...4bb85e )
by Esteban De La Fuente
08:41
created

ConsumeWebserviceJob::callServiceFunction()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 104
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 24.8039

Importance

Changes 0
Metric Value
eloc 49
dl 0
loc 104
c 0
b 0
f 0
ccs 21
cts 50
cp 0.42
rs 7.5571
cc 9
nc 6
nop 5
crap 24.8039

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\Package\Billing\Component\Integration\Worker\SiiLazy\Job;
26
27
use Derafu\Lib\Core\Foundation\Abstract\AbstractJob;
28
use Derafu\Lib\Core\Foundation\Contract\JobInterface;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Entity\Xml as XmlDocument;
31
use libredte\lib\Core\Package\Billing\Component\Integration\Contract\SiiRequestInterface;
32
use libredte\lib\Core\Package\Billing\Component\Integration\Exception\SiiConsumeWebserviceException;
33
use SoapClient;
34
use SoapFault;
35
36
/**
37
 * Clase para consumir los servicios web SOAP del SII.
38
 */
39
class ConsumeWebserviceJob extends AbstractJob implements JobInterface
40
{
41
    /**
42
     * Realiza una solicitud a un servicio web del SII mediante el uso de WSDL.
43
     *
44
     * Este método prepara y normaliza los datos recibidos y llama al método
45
     * que realmente hace la consulta al SII: callServiceFunction().
46
     *
47
     * @param SiiRequestInterface $request Datos de la solicitud al SII.
48
     * @param string $service Nombre del servicio del SII que se consumirá.
49
     * @param string $function Nombre de la función que se ejecutará en el
50
     * servicio web del SII.
51
     * @param array|int $args Argumentos que se pasarán al servicio web.
52
     * @param int|null $retry Intentos que se realizarán como máximo para
53
     * obtener respuesta.
54
     * @return XmlInterface Documento XML con la respuesta del servicio web.
55
     * @throws SiiConsumeWebserviceException En caso de error.
56
     */
57 1
    public function sendRequest(
58
        SiiRequestInterface $request,
59
        string $service,
60
        string $function,
61
        array|int $args = [],
62
        ?int $retry = null
63
    ): XmlInterface {
64
        // Revisar si se pasó en $args el valor de $retry.
65
        // @scrutinizer ignore-type-check
66 1
        if (is_int($args)) {
0 ignored issues
show
introduced by
The condition is_int($args) is always false.
Loading history...
67
            $retry = $args;
68
            $args = [];
69
        }
70
71
        // Definir el WSDL que se debe utilizar.
72 1
        $wsdl = $this->getWsdlUri($request, $service);
73
74
        // Resolver el valor de $retry.
75 1
        $retry = $request->getReintentos($retry);
76
77
        // Definir las opciones para consumir el servicio web.
78 1
        $soapClientOptions = $this->createSoapClientOptions($request);
79
80
        // Realizar la llamada a la función en el servicio web del SII.
81 1
        return $this->callServiceFunction(
82 1
            $wsdl,
83 1
            $function,
84 1
            $args,
85 1
            $soapClientOptions,
86 1
            $retry
87 1
        );
88
    }
89
90
    /**
91
     * Método para obtener el XML del WSDL (Web Services Description Language)
92
     * del servicio del SII que se desea consumir.
93
     *
94
     * @param SiiRequestInterface $request Datos de la solicitud al SII.
95
     * @param string $servicio Servicio para el cual se desea obtener su WSDL.
96
     * @return string Ubicación del WSDL del servicio según el ambiente que
97
     * esté configurado. Entrega, normalmente, un archivo local para un WSDL
98
     * del ambiente de certificación y siempre una URL para un WSDL del
99
     * ambiente de producción.
100
     */
101 1
    private function getWsdlUri(SiiRequestInterface $request, string $servicio): string
102
    {
103 1
        $ambiente = $request->getAmbiente();
104
105
        // Algunos WSDL del ambiente de certificación no funcionan tal cual los
106
        // provee SII. Lo anterior ya que apuntan a un servidor llamado
107
        // nogal.sii.cl el cual no es accesible desde Internet. Posiblemente es
108
        // un servidor local del SII para desarrollo. Así que LibreDTE tiene
109
        // para el ambiente de certificación WSDL modificados para funcionar
110
        // con el servidor de pruebas (maullin.sii.cl). Estos WSDL se usan
111
        // siempre al solicitar el WSDL del ambiente de certificación.
112
        // Cambios basados en: http://stackoverflow.com/a/28464354/3333009
113 1
        if ($ambiente->isCertificacion()) {
114
            $wsdl = $ambiente->getWsdlPath($servicio);
115
            if ($wsdl !== null) {
116
                return $wsdl;
117
            }
118
        }
119
120
        // Los WSDL para el ambiente de producción son directamente los
121
        // proporcionados por el SII y que están definidos en la configuración.
122
        // Si por cualquier motivo un WSDL de un servicio para el ambiente de
123
        // certificación no existe localmente en LibreDTE, también se entregará
124
        // el WSDL oficial del SII.
125 1
        return $ambiente->getWsdl($servicio);
126
    }
127
128
    /**
129
     * Ejecuta una función en un servicio web del SII mediante el uso de WSDL.
130
     *
131
     * @param string $wsdl WSDL del servicio del SII que se consumirá.
132
     * @param string $function Nombre de la función que se ejecutará,
133
     * @param array $args Argumentos que se pasarán al servicio web.
134
     * @param array $soapClientOptions Opciones del cliente SOAP.
135
     * @param int $retry Intentos que se realizarán como máximo.
136
     * @return XmlInterface Documento XML con la respuesta del servicio web.
137
     * @throws SiiConsumeWebserviceException En caso de error.
138
     */
139 1
    private function callServiceFunction(
140
        string $wsdl,
141
        string $function,
142
        array $args,
143
        array $soapClientOptions,
144
        int $retry
145
    ): XmlInterface {
146
        // Preparar cliente SOAP.
147
        try {
148 1
            $soap = new SoapClient($wsdl, $soapClientOptions);
149
        } catch (SoapFault $e) {
150
            $message = $e->getMessage();
151
            if (
152
                isset($e->getTrace()[0]['args'][1])
153
                && is_string($e->getTrace()[0]['args'][1])
154
            ) {
155
                $message .= ': ' . $e->getTrace()[0]['args'][1];
156
            }
157
            throw new SiiConsumeWebserviceException(sprintf(
158
                'Ocurrió un error al crear el cliente SOAP para la API del SII con el WSDL %s. %s',
159
                $wsdl,
160
                $message
161
            ));
162
        }
163
164
        // Argumentos adicionales para la llamada al servicio web SOAP mediante
165
        // __soapCall().
166 1
        $options = null;
167
168
        // En el WSDL indicadas como soap:header.
169 1
        $requestHeaders = [];
170
171
        // Si el SII enviase cabeceras SOAP devuelta.
172 1
        $responseHeaders = [];
173
174
        // Para almacenar la respuesta de la llamada a la API SOAP del SII.
175 1
        $responseBody = null;
176
177
        // Para ir almacenando los errores, si existen, de cada intento.
178 1
        $errors = [];
179
180
        // Ejecutar la función que se ha solicitado del servicio web a través
181
        // del cliente SOAP preparado previamente.
182
        // Se realizarán $retry intentos de consulta. O sea, si $retry > 0 se
183
        // hará una consulta más $retry - 1 reintentos.
184 1
        for ($i = 0; $i < $retry; $i++) {
185
            try {
186
                // Se realiza la llamada a la función en el servicio web.
187 1
                $responseBody = $soap->__soapCall(
188 1
                    $function,
189 1
                    $args,
190 1
                    $options,
191 1
                    $requestHeaders,
192 1
                    $responseHeaders
193 1
                );
194
                // Si la llamada no falló (no hubo excepción), se rompe el
195
                // ciclo de reintentos.
196 1
                break;
197
            } catch (SoapFault $e) {
198
                $message = $e->getMessage();
199
                if (
200
                    isset($e->getTrace()[0]['args'][1])
201
                    && is_string($e->getTrace()[0]['args'][1])
202
                ) {
203
                    $message .= ': ' . $e->getTrace()[0]['args'][1];
204
                }
205
                $errors[] = sprintf(
206
                    'Error al ejecutar la función %s en el servicio web SOAP del SII ($i = %d): %s',
207
                    $function,
208
                    $i,
209
                    $message
210
                );
211
                $responseBody = null;
212
                // El reitento será con "exponential backoff", por lo que se
213
                // hace una pausa de 0.2 * $retry segundos antes de volver a
214
                // intentar llamar a la función del servicio web.
215
                usleep(200000 * $retry);
216
            }
217
        }
218
219
        // Si la respuesta es `null` significa que ninguno de los intentos de
220
        // llamadas a la función del servicio web fue exitoso.
221 1
        if ($responseBody === null) {
222
            throw new SiiConsumeWebserviceException(sprintf(
223
                'No se obtuvo respuesta de la función %s del servicio web SOAP del SII después de %d intentos. %s',
224
                $function,
225
                $retry,
226
                implode(' ', $errors)
227
            ));
228
        }
229
230
        // El SII indica que la respuesta que envía es:
231
        //   Content-Type: text/xml;charset=utf-8
232
        // Sin embargo, parece que indica que es UTF-8 pero envía contenido
233
        // codificado con ISO-8859-1 (que es lo esperable del SII). Lo que hace
234
        // que los caracteres especiales se obtengan como "�", por lo cual se
235
        // reemplazan en la respuesta por "?" para que sea "un poco" más
236
        // legible la respuesta del SII.
237 1
        $responseBody = str_replace(['�', '�'], '?', $responseBody);
238
239
        // Entregar el resultado como un documento XML.
240 1
        $xmlDocument = new XmlDocument();
241 1
        $xmlDocument->loadXml($responseBody);
242 1
        return $xmlDocument;
243
    }
244
245
    /**
246
     * Define las opciones para consumir el servicio web del SII mediante SOAP.
247
     *
248
     * @param SiiRequestInterface $request Datos de la solicitud al SII.
249
     * @return array Arreglo con las opciones para SoapClient.
250
     */
251 1
    private function createSoapClientOptions(SiiRequestInterface $request): array
252
    {
253
        // Configuración de caché para SOAP.
254 1
        ini_set('soap.wsdl_cache_enabled', 3600);
255 1
        ini_set('soap.wsdl_cache_ttl', 3600);
256
257
        // Opciones base.
258 1
        $options = [
259 1
            'encoding' => 'ISO-8859-1',
260
            //'trace' => true, // Permite usar __getLastResponse().
261 1
            'exceptions' => true, // Lanza SoapFault en caso de error.
262 1
            'cache_wsdl' => WSDL_CACHE_MEMORY, // WSDL_CACHE_DISK o WSDL_CACHE_MEMORY.
263 1
            'keep_alive' => false,
264 1
            'stream_context' => [
265 1
                'http' => [
266 1
                    'header' => [
267 1
                        'User-Agent: Mozilla/5.0 (compatible; PROG 1.0; +https://www.libredte.cl)',
268 1
                    ],
269 1
                ],
270 1
            ],
271 1
        ];
272
273
        // Si no se debe verificar el certificado SSL del servidor del SII se
274
        // asigna al "stream_context" dicha configuración.
275 1
        if (!$request->getVerificarSsl()) {
276
            $options['stream_context']['ssl'] = [
277
                'verify_peer' => false,
278
                'verify_peer_name' => false,
279
                'allow_self_signed' => true,
280
            ];
281
        }
282
283
        // Crear el "stream context" verdadero con las opciones definidas.
284 1
        $options['stream_context'] = stream_context_create(
285 1
            $options['stream_context']
286 1
        );
287
288
        // Retornar las opciones para el SoapClient.
289 1
        return $options;
290
    }
291
}
292