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