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
![]() |
|||||
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
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
![]() |
|||||
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
|
|||||
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 |