HttpCurlClient::parseResponseStatus()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 8
cp 0
rs 10
cc 3
nc 4
nop 1
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Cliente de API en PHP.
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 GNU Lesser General Public License (LGPL) publicada
11
 * por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia,
12
 * o (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 GNU Lesser General
17
 * Public License (LGPL) para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la GNU Lesser General Public License
20
 * (LGPL) junto a este programa. En caso contrario, consulte
21
 * <http://www.gnu.org/licenses/lgpl.html>.
22
 */
23
24
namespace libredte\api_client;
25
26
/**
27
 * Clase HttpCurlClient para realizar consultas HTTP utilizando cURL.
28
 *
29
 * Esta clase proporciona una interfaz para realizar peticiones HTTP, como GET
30
 * y POST, utilizando cURL. Ofrece configuración de SSL y manejo de errores de
31
 * cURL.
32
 */
33
class HttpCurlClient
34
{
35
    /**
36
     * Indica si se debe validar el certificado SSL del servidor.
37
     *
38
     * @var boolean
39
     */
40
    private $sslcheck = true;
41
42
    /**
43
     * Historial de errores de las consultas HTTP mediante cURL.
44
     *
45
     * @var array
46
     */
47
    private $errors = [];
48
49
    /**
50
     * Devuelve los errores ocurridos en las peticiones HTTP.
51
     *
52
     * Este método devuelve un array con los errores generados por cURL en las
53
     * peticiones HTTP realizadas.
54
     *
55
     * @return array Lista de errores de cURL.
56
     */
57
    public function getErrors(): array
58
    {
59
        return $this->errors;
60
    }
61
62
    /**
63
     * Devuelve el último error ocurrido en una petición HTTP.
64
     *
65
     * Este método devuelve el último error generado por cURL en una petición
66
     * HTTP.
67
     *
68
     * @return string Descripción del último error de cURL.
69
     */
70
    public function getLastError(): string
71
    {
72
        return $this->errors[count($this->errors) - 1];
73
    }
74
75
    /**
76
     * Configura las opciones de SSL para las peticiones HTTP.
77
     *
78
     * Este método permite activar o desactivar la verificación del certificado
79
     * SSL del servidor.
80
     *
81
     * @param boolean $sslcheck Activar o desactivar la verificación del
82
     * certificado SSL.
83
     */
84
    public function setSSL(bool $sslcheck = true): void
85
    {
86
        $this->sslcheck = $sslcheck;
87
    }
88
89
    /**
90
     * Realiza una solicitud HTTP a una URL.
91
     *
92
     * Este método ejecuta una petición HTTP utilizando cURL y devuelve la
93
     * respuesta.
94
     * Soporta varios métodos HTTP como GET, POST, PUT, DELETE, etc.
95
     *
96
     * @param string $method Método HTTP a utilizar.
97
     * @param string $url URL a la que se realiza la petición.
98
     * @param mixed $data Datos a enviar en la petición.
99
     * @param array $headers Cabeceras HTTP a enviar.
100
     * @return array|false Respuesta HTTP o false en caso de error.
101
     */
102
    public function query(
103
        string $method,
104
        string $url,
105
        mixed $data = [],
106
        array $headers = []
107
    ): array|bool {
108
        // preparar datos
109
        if ($data && $method != 'GET') {
110
            if (isset($data['@files'])) {
111
                $files = $data['@files'];
112
                unset($data['@files']);
113
                $data = ['@data' => json_encode($data)];
114
                foreach ($files as $key => $file) {
115
                    $data[$key] = $file;
116
                }
117
            } else {
118
                $data = json_encode($data);
119
                $headers['Content-Length'] = strlen($data);
120
            }
121
        }
122
        // inicializar curl
123
        $curl = curl_init();
124
        // asignar método y datos dependiendo de si es GET u otro método
125
        if ($method == 'GET') {
126
            if (is_array($data)) {
127
                $data = http_build_query($data);
128
            }
129
            if ($data) {
130
                $url = sprintf("%s?%s", $url, $data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array and array<string,string>; however, parameter $values of sprintf() does only seem to accept double|integer|string, 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

130
                $url = sprintf("%s?%s", $url, /** @scrutinizer ignore-type */ $data);
Loading history...
131
            }
132
        } else {
133
            curl_setopt($curl, CURLOPT_POST, 1);
134
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
135
            if ($data) {
136
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
137
            }
138
        }
139
        // asignar cabecera
140
        foreach ($headers as $key => &$value) {
141
            $value = $key.': '.$value;
142
        }
143
        // asignar cabecera
144
        curl_setopt($curl, CURLOPT_HTTPHEADER, array_values($headers));
145
        // realizar consulta a curl recuperando cabecera y cuerpo
146
        curl_setopt($curl, CURLOPT_URL, $url);
147
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
148
        curl_setopt($curl, CURLOPT_HEADER, 1);
149
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->sslcheck);
150
        $response = curl_exec($curl);
151
        if (!$response) {
152
            $this->errors[] = curl_error($curl);
153
            return false;
154
        }
155
        $headers_size = curl_getinfo(handle: $curl, option: CURLINFO_HEADER_SIZE);
156
        // cerrar conexión de curl
157
        curl_close($curl);
158
        // entregar respuesta de la solicitud
159
        $response_headers = $this->parseResponseHeaders(
160
            substr(
161
                string: $response,
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $string of substr() does only seem to accept string, 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

161
                /** @scrutinizer ignore-type */ string: $response,
Loading history...
162
                offset: 0,
163
                length: $headers_size
164
            )
165
        );
166
        $body = substr($response, $headers_size);
167
        $json = json_decode(json: $body, associative: true);
168
        return [
169
            'status' => $this->parseResponseStatus($response_headers[0]),
170
            'header' => $response_headers,
171
            'body' => $json !== null ? $json : $body,
172
        ];
173
    }
174
175
    /**
176
     * Método que procesa y convierte la cabecera en texto plano a un arreglo
177
     * asociativo.
178
     *
179
     * Convierte las cabeceras HTTP dadas en texto plano a un arreglo
180
     * asociativo. Si una cabecera aparece más de una vez, su valor será un
181
     * arreglo con todos sus valores.
182
     *
183
     * @param string $headers_txt Cabeceras HTTP en formato de texto plano.
184
     * @return array Arreglo asociativo con las cabeceras procesadas.
185
     */
186
    private function parseResponseHeaders(string $headers_txt): array
187
    {
188
        $headers = [];
189
        $lineas = explode("\n", $headers_txt);
190
        foreach ($lineas as &$linea) {
191
            $linea = trim($linea);
192
            if (!isset($linea[0])) {
193
                continue;
194
            }
195
            if (strpos($linea, ':')) {
196
                list($key, $value) = explode(
197
                    separator: ':',
198
                    string: $linea,
199
                    limit: 2
200
                );
201
            } else {
202
                $key = 0;
203
                $value = $linea;
204
            }
205
            $key = trim(strval($key));
206
            $value = trim($value);
207
            if (!isset($headers[$key])) {
208
                $headers[$key] = $value;
209
            } elseif (!is_array($headers[$key])) {
210
                $aux = $headers[$key];
211
                $headers[$key] = [$aux, $value];
212
            } else {
213
                $headers[$key][] = $value;
214
            }
215
        }
216
        return $headers;
217
    }
218
219
    /**
220
     * Método que procesa la línea de estado de la respuesta HTTP y extrae
221
     * información útil.
222
     *
223
     * Extrae el protocolo, el código de estado y el mensaje del estado de la
224
     * línea de respuesta HTTP.
225
     * Útil para entender y manejar la respuesta HTTP.
226
     *
227
     * @param array|string $response_line Línea de respuesta HTTP.
228
     * @return array Arreglo con información del estado, incluyendo protocolo,
229
     * código y mensaje.
230
     */
231
    private function parseResponseStatus(array|string $response_line): array
232
    {
233
        if (is_array($response_line)) {
0 ignored issues
show
introduced by
The condition is_array($response_line) is always true.
Loading history...
234
            $response_line = $response_line[count($response_line) - 1];
235
        }
236
        $parts = explode(separator: ' ', string: $response_line, limit: 3);
237
        return [
238
            'protocol' => $parts[0],
239
            'code' => $parts[1],
240
            'message' => !empty($parts[2]) ? $parts[2] : null,
241
        ];
242
    }
243
}
244