Passed
Push — master ( 6c6b0c...6695f9 )
by
unknown
03:05
created

ApiClient::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
c 1
b 0
f 0
rs 10
cc 1
nc 1
nop 3
1
<?php
2
3
/**
4
 * API Gateway: Cliente de API en PHP.
5
 * Copyright (C) API Gateway <https://www.apigateway.cl>
6
 *
7
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
8
 * bajo los términos de la GNU Lesser General Public License (LGPL) publicada
9
 * por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia,
10
 * o (a su elección) cualquier versión posterior de la misma.
11
 *
12
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
13
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
14
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la GNU Lesser General
15
 * Public License (LGPL) para obtener una información más detallada.
16
 *
17
 * Debería haber recibido una copia de la GNU Lesser General Public License
18
 * (LGPL) junto a este programa. En caso contrario, consulte
19
 * <http://www.gnu.org/licenses/lgpl.html>.
20
 */
21
22
 namespace apigatewaycl\api_client;
23
24
/**
25
 * Cliente de la API de API Gateway.
26
 *
27
 * Esta clase permite interactuar con la API de API Gateway, manejando
28
 * las solicitudes HTTP, la autenticación y el manejo de respuestas.
29
 */
30
class ApiClient
31
{
32
33
    /**
34
     * La URL base de la API de API Gateway.
35
     *
36
     * @var string
37
     */
38
    private $api_url = 'https://apigateway.cl';
39
40
    /**
41
     * El prefijo para las rutas de la API.
42
     *
43
     * @var string
44
     */
45
    private $api_prefix = '/api';
46
47
    /**
48
     * La versión de la API a utilizar.
49
     *
50
     * @var string
51
     */
52
    private $api_version = 'v1';
53
54
    /**
55
     * El token de autenticación para la API.
56
     *
57
     * @var string|null
58
     */
59
    private $api_token = null;
60
61
    /**
62
     * La última URL utilizada en la solicitud HTTP.
63
     *
64
     * @var string|null
65
     */
66
    private $last_url = null;
67
68
    /**
69
     * La última respuesta recibida de la API.
70
     *
71
     * @var \Psr\Http\Message\ResponseInterface|null
72
     */
73
    private $last_response = null;
74
75
    /**
76
     * Constructor del cliente de la API.
77
     *
78
     * @param string|null $token Token de autenticación para la API.
79
     * @param string|null $url URL base de la API.
80
     */
81
    public function __construct($token = null, $url = null)
82
    {
83
        $this->api_token = $token ?: $this->env('APIGATEWAY_API_TOKEN');
84
        if (!$this->api_token) {
85
            throw new ApiException('APIGATEWAY_API_TOKEN missing');
86
        }
87
88
        $this->api_url = $url ?: $this->env('APIGATEWAY_API_URL') ?: $this->api_url;
89
    }
90
91
    /**
92
     * Establece la URL base de la API.
93
     *
94
     * @param string $url URL base.
95
     * @return $this
96
     */
97
    public function setUrl($url)
98
    {
99
        $this->api_url = $url;
100
        return $this;
101
    }
102
103
    /**
104
     * Establece el token de autenticación.
105
     *
106
     * @param string $token Token de autenticación.
107
     * @return $this
108
     */
109
    public function setToken($token)
110
    {
111
        $this->api_token = $token;
112
        return $this;
113
    }
114
115
    /**
116
     * Obtiene la última URL utilizada en la solicitud HTTP.
117
     *
118
     * @return string|null
119
     */
120
    public function getLastUrl()
121
    {
122
        return $this->last_url;
123
    }
124
125
    /**
126
     * Obtiene la última respuesta recibida de la API.
127
     *
128
     * @return \Psr\Http\Message\ResponseInterface|null
129
     */
130
    public function getLastResponse()
131
    {
132
        return $this->last_response;
133
    }
134
135
    /**
136
     * Obtiene el cuerpo de la última respuesta HTTP.
137
     *
138
     * Este método devuelve el cuerpo de la respuesta de la última
139
     * solicitud HTTP realizada utilizando este cliente API.
140
     *
141
     * @return string El cuerpo de la respuesta HTTP.
142
     * @throws ApiException Si no hay respuesta previa o el cuerpo no se puede obtener.
143
     */
144
    public function getBody()
145
    {
146
        if (!$this->last_response) {
147
            throw new ApiException('No hay una respuesta HTTP previa para obtener el cuerpo.');
148
        }
149
150
        return (string)$this->last_response->getBody();
151
    }
152
153
    /**
154
     * Obtiene el cuerpo de la última respuesta HTTP y lo decodifica de JSON.
155
     *
156
     * Este método devuelve el cuerpo de la respuesta de la última
157
     * solicitud HTTP realizada por este cliente API, decodificándolo de
158
     * formato JSON a un arreglo asociativo de PHP.
159
     *
160
     * @return array El cuerpo de la respuesta HTTP decodificado como un arreglo.
161
     * @throws ApiException Si no hay respuesta previa o el cuerpo no se puede decodificar.
162
     */
163
    public function getBodyDecoded()
164
    {
165
        $body = $this->getBody();
166
167
        $decodedBody = json_decode($body, true);
168
169
        if (json_last_error() !== JSON_ERROR_NONE) {
170
            throw new ApiException('Error al decodificar JSON: ' . json_last_error_msg());
171
        }
172
173
        return $decodedBody;
174
    }
175
176
    /**
177
     * Convierte la última respuesta HTTP en un arreglo asociativo.
178
     *
179
     * Este método transforma la última respuesta HTTP recibida en un arreglo
180
     * asociativo, que incluye información del estado HTTP, encabezados y el
181
     * cuerpo de la respuesta, ya sea en formato de texto o decodificado de JSON.
182
     *
183
     * @return array Arreglo asociativo con la información de la respuesta.
184
     * @throws ApiException Si se encuentra un error en el proceso.
185
     */
186
    public function toArray()
187
    {
188
        if (!$this->last_response) {
189
            throw new ApiException('No hay una respuesta HTTP previa para procesar.');
190
        }
191
192
        $headers = $this->getLastResponse()->getHeaders();
193
        foreach ($headers as &$header) {
194
            $header = isset($header[1]) ? $header : $header[0];
195
        }
196
197
        $statusCode = $this->getLastResponse()->getStatusCode();
198
        $contentType = $this->getLastResponse()->getHeader('content-type')[0];
199
200
        // Procesar el cuerpo de la respuesta según el tipo de contenido
201
        if ($contentType == 'application/json') {
202
            $body = $this->getBodyDecoded();
203
            if ($body === null) {
0 ignored issues
show
introduced by
The condition $body === null is always false.
Loading history...
204
                $body = $this->getBody();
205
                $body = $body ?: $this->getError()->message;
206
            }
207
        } else {
208
            $body = $this->getBody();
209
            $body = $body ?: $this->getError()->message;
210
        }
211
212
        // Manejar respuestas con error
213
        if ($statusCode != 200) {
214
            if (!empty($body['message'])) {
215
                $body = $body['message'];
216
            } elseif (!empty($body['exception'])) {
217
                $body = $this->getError()->message;
218
            } else {
219
                $body = 'Error no determinado: ' . json_encode($body);
220
            }
221
        }
222
223
        return [
224
            'status' => [
225
                'protocol' => $this->getLastResponse()->getProtocolVersion(),
226
                'code' => $statusCode,
227
                'message' => $this->getLastResponse()->getReasonPhrase(),
228
            ],
229
            'header' => $headers,
230
            'body' => $body,
231
        ];
232
    }
233
234
    /**
235
     * Realiza una solicitud GET a la API.
236
     *
237
     * @param string $resource Recurso de la API al cual realizar la solicitud.
238
     * @param array $headers Encabezados adicionales para incluir en la solicitud.
239
     * @param array $options Arreglo con las opciones de la solicitud HTTP.
240
     * @return \Psr\Http\Message\ResponseInterface|null
241
     */
242
    public function get($resource, array $headers = [], array $options = [])
243
    {
244
        return $this->consume($resource, [], $headers, 'GET', $options)->getLastResponse();
245
    }
246
247
    /**
248
     * Realiza una solicitud POST a la API.
249
     *
250
     * @param string $resource Recurso de la API al cual realizar la solicitud.
251
     * @param array $data Datos a enviar en la solicitud.
252
     * @param array $headers Encabezados adicionales para incluir en la solicitud.
253
     * @param array $options Arreglo con las opciones de la solicitud HTTP.
254
     * @return \Psr\Http\Message\ResponseInterface|null
255
     */
256
    public function post($resource, array $data, array $headers = [], array $options = [])
257
    {
258
        return $this->consume($resource, $data, $headers, 'POST', $options)->getLastResponse();
259
    }
260
261
    /**
262
     * Realiza una solicitud PUT a la API.
263
     *
264
     * @param string $resource Recurso de la API al cual realizar la solicitud.
265
     * @param array $data Datos a enviar en la solicitud.
266
     * @param array $headers Encabezados adicionales para incluir en la solicitud.
267
     * @param array $options Arreglo con las opciones de la solicitud HTTP.
268
     * @return \Psr\Http\Message\ResponseInterface|null
269
     */
270
    public function put($resource, array $data, array $headers = [], array $options = [])
271
    {
272
        return $this->consume($resource, $data, $headers, 'PUT', $options)->getLastResponse();
273
    }
274
275
    /**
276
     * Realiza una solicitud DELETE a la API.
277
     *
278
     * @param string $resource Recurso de la API al cual realizar la solicitud.
279
     * @param array $headers Encabezados adicionales para incluir en la solicitud.
280
     * @param array $options Arreglo con las opciones de la solicitud HTTP.
281
     * @return \Psr\Http\Message\ResponseInterface|null
282
     */
283
    public function delete($resource, array $headers = [], array $options = [])
284
    {
285
        return $this->consume($resource, [], $headers, 'DELETE', $options)->getLastResponse();
286
    }
287
288
    /**
289
     * Realiza una solicitud HTTP a la API.
290
     *
291
     * Este método envía una solicitud HTTP a la API de API Gateway, utilizando
292
     * los parámetros especificados y manejando la autenticación y la respuesta.
293
     *
294
     * @param string $resource El recurso de la API al cual realizar la solicitud.
295
     * @param array $data Datos a enviar en la solicitud (para métodos POST y PUT).
296
     * @param array $headers Encabezados adicionales para incluir en la solicitud.
297
     * @param string|null $method Método HTTP a utilizar (GET, POST, PUT, DELETE).
298
     * @param array $options Arreglo con las opciones de la solicitud HTTP.
299
     * @return $this Instancia actual del cliente para encadenar llamadas.
300
     * @throws ApiException Si se produce un error en la solicitud.
301
     */
302
    public function consume($resource, $data = [], array $headers = [], $method = null, $options = [])
303
    {
304
        $this->last_response = null;
305
        if (!$this->api_token) {
306
            throw new ApiException('Falta especificar token para autenticación.', 400);
307
        }
308
        $method = $method ?: ($data ? 'POST' : 'GET');
309
        $client = new \GuzzleHttp\Client();
310
        $this->last_url = $this->api_url . $this->api_prefix . '/'. $this->api_version . $resource;
311
312
        // preparar cabeceras que se usarán
313
        $options[\GuzzleHttp\RequestOptions::HEADERS] = array_merge([
314
            'Authorization' => 'Bearer ' . $this->api_token,
315
            'Content-Type' => 'application/json',
316
            'Accept' => 'application/json',
317
        ], $headers);
318
319
        // agregar datos de la consulta si se pasaron (POST o PUT)
320
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
321
            $options[\GuzzleHttp\RequestOptions::JSON] = $data;
322
        }
323
324
        // Ejecutar consulta al SII.
325
        try {
326
            $this->last_response = $client->request($method, $this->last_url, $options);
327
        } catch (\GuzzleHttp\Exception\GuzzleException $e) {
328
            // Obtener la respuesta de la llamada.
329
            $this->last_response = $e->getResponse();
0 ignored issues
show
Bug introduced by
The method getResponse() does not exist on GuzzleHttp\Exception\GuzzleException. It seems like you code against a sub-type of GuzzleHttp\Exception\GuzzleException such as GuzzleHttp\Exception\RequestException. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

329
            /** @scrutinizer ignore-call */ 
330
            $this->last_response = $e->getResponse();
Loading history...
330
            // Se lanza la excepción.
331
            $this->throwException();
332
        }
333
334
        // Entregar respuesta (contenida en el mismo objeto del cliente).
335
        return $this;
336
    }
337
338
    /**
339
     * Extrae información detallada sobre un error a partir de la última respuesta HTTP.
340
     *
341
     * Este método analiza la última respuesta HTTP para extraer información
342
     * detallada sobre un error que ocurrió durante la solicitud. Devuelve un
343
     * objeto con los detalles del error, incluyendo el código y el mensaje.
344
     *
345
     * @return object Detalles del error con propiedades 'code' y 'message'.
346
     */
347
    private function getError()
348
    {
349
        $data = $this->getBodyDecoded();
350
        $response = $this->getLastResponse();
351
        $statusCode = $response ? $response->getStatusCode() : null;
352
        $reasonPhrase = $response ? $response->getReasonPhrase() : 'Sin respuesta';
353
354
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
355
            $code = isset($data['code']) ? $data['code'] : $statusCode;
356
            $message = isset($data['message']) ? $data['message'] : $reasonPhrase;
357
        } else {
358
            $code = $statusCode;
359
            $message = $reasonPhrase;
360
        }
361
362
        // Se maneja el caso donde no se encuentra un mensaje de error específico
363
        if (!$message || $message === '') {
364
            $message = '[API Gateway] Código HTTP ' . $code . ': ' . $reasonPhrase;
365
        }
366
367
        return (object)[
368
            'code' => $code,
369
            'message' => $message,
370
        ];
371
    }
372
373
    /**
374
     * Lanza una ApiException con los detalles del último error.
375
     *
376
     * Este método utiliza la información obtenida del método getError() para
377
     * lanzar una ApiException con un mensaje de error detallado y un código
378
     * de error asociado. Se utiliza para manejar errores de la API de manera
379
     * uniforme en toda la clase.
380
     *
381
     * @throws ApiException Lanza una excepción con detalles del error.
382
     */
383
    private function throwException()
384
    {
385
        $error = $this->getError();
386
        throw new ApiException($error->message, $error->code);
387
    }
388
389
    /**
390
     * Obtiene el valor de una variable de entorno.
391
     *
392
     * @param string $name Nombre de la variable de entorno.
393
     * @return string|null Valor de la variable de entorno o null si no está definida.
394
     */
395
    private function env($name)
396
    {
397
        return function_exists('env') ? env($name) : getenv($name);
398
    }
399
400
}
401