LibreDTE /
libredte-api-client-php
| 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
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
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
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
|
|||||
| 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 |