src/Services/ApiConsumerService.php line 230

Open in your IDE?
  1. <?php
  2. namespace App\Services;
  3. use App\Controller\Log;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Monolog\Logger;
  6. use Psr\Log\LoggerInterface;
  7. use Symfony\Component\Cache\Adapter\FilesystemAdapter;
  8. use Symfony\Component\Cache\CacheItem;
  9. use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
  10. use Symfony\Component\Mime\Part\Multipart\FormDataPart;
  11. use Symfony\Component\Security\Core\Security;
  12. use Symfony\Contracts\HttpClient\HttpClientInterface;
  13. use Symfony\Contracts\Translation\TranslatorInterface;
  14. class ApiConsumerService
  15. {
  16. protected $cache;
  17. protected $em;
  18. private $httpClient;
  19. private $security;
  20. private $translator;
  21. protected $projectDir;
  22. protected $logger;
  23. public function __construct(
  24. HttpClientInterface $hermesClient,
  25. Security $security,
  26. TranslatorInterface $translator,
  27. EntityManagerInterface $em,
  28. $projectDir,
  29. LoggerInterface $cacheLogger
  30. ) {
  31. $this->httpClient = $hermesClient;
  32. $this->security = $security;
  33. $this->translator = $translator;
  34. $this->cache = new FilesystemAdapter('', 0, $projectDir."/var/cache");
  35. $this->em = $em;
  36. $this->projectDir = $projectDir;
  37. $this->logger=$cacheLogger;
  38. }
  39. /**
  40. * Hace una petición GET sin tener en cuenta la caché (sin ver si está en caché y sin almacenar).
  41. * Se utiliza en peticiones de cambio como change-subscription-status que realmente no obtenemos un objeto cacheable.
  42. */
  43. public function getDataNoCache($endpoint, $queryParams = [], $global=false, $expire=null, $headers=null) {
  44. return $this->getData($endpoint, $queryParams = [], false, $global=false, $expire=null, $headers=null, false);
  45. }
  46. public function getData($endpoint, $queryParams = [], $fromCache = true, $global = false, $expire = null, $headers = null, $writeCache = true)
  47. {
  48. // Añadimos el parámetro '$gzip' para indicar el nivel de compresión (9 en este caso).
  49. $queryParams['$gzip'] = 9;
  50. // También calculamos el tiempo de expiración '$expire', utilizando un valor predeterminado
  51. // de la configuración ($_ENV["EXPIRE"]) con una variación aleatoria del 20%.
  52. $queryParams['$expire'] = $expire ?? $_ENV["EXPIRE"] + rand(-($expire ?? $_ENV["EXPIRE"] * 0.2), $expire ?? $_ENV["EXPIRE"] * 0.2);
  53. // Limpiamos los parámetros de consulta ($queryParams) eliminando claves reservadas ('$gzip', '$expire', '$nocache').
  54. // Esto evita que estas claves afecten la generación de la clave de caché o las consultas al API.
  55. foreach (($cleanParams = $queryParams ?? []) as $parameter => $value) {
  56. if (in_array($parameter, ['$gzip', '$expire', '$nocache'])) unset($cleanParams[$parameter]);
  57. }
  58. // Generamos una clave única para la caché ($key) utilizando:
  59. // - La URL del endpoint.
  60. // - Los parámetros de consulta limpiados.
  61. // - Los encabezados proporcionados o predeterminados.
  62. $key = md5($endpoint . json_encode($cleanParams) . json_encode($headers ?? $this->getHeaders($global)));
  63. // Registramos en el log la clave generada y un resumen de los parámetros de consulta y encabezados.
  64. $this->logger->debug("{$key} => {$endpoint} Q=(" . md5(json_encode($queryParams)) . ") H=(" . md5(json_encode($headers)) . ")");
  65. // Recuperamos los elementos de caché para la clave específica ($key) y el historial de solicitudes.
  66. $cacheResponse = $this->cache->getItem($key);
  67. // Si el flag $fromCache es true, intentamos obtener la respuesta desde la caché.
  68. //TODO Revisar no va bien la cache
  69. if ($fromCache) {
  70. $this->logger->debug("{$endpoint} Intentamos obtener respuesta desde la caché...");
  71. // Si la respuesta está en caché, la devolvemos directamente.
  72. $this->logger->debug("Cache content: " . json_encode($cacheResponse->get()));
  73. if ($cacheResponse->isHit()) {
  74. $this->logger->debug("Get from cache ...");
  75. $this->updateCacheHistorico($cacheResponse, $key, $endpoint, $queryParams, $global, $headers);
  76. $response = json_decode($cacheResponse->get(),true);
  77. // DEBUG-INC666-G consumer-hydrate-evento-fields
  78. // Log claves devueltas desde cache cuando endpoint es evento; permite detectar
  79. // payload cacheado pre-fix sin los nuevos campos (is_publico, inscribir, fecha_*).
  80. if (function_exists('error_log') && is_string($endpoint) && strpos($endpoint, 'evento/') !== false) {
  81. $keysList = is_array($response) ? implode(',', array_keys($response)) : 'not-array';
  82. error_log(sprintf('[DEBUG-INC666-G] ApiConsumerService cache-hit endpoint=%s keys=%s', $endpoint, $keysList));
  83. }
  84. if (@$response['status']>=300) {
  85. if (@$response['status']==400) return null;
  86. }
  87. return $response['content'];
  88. } else {
  89. $queryParams['$nocache'] = true;
  90. $this->logger->debug("No hay Hit -> salimos...");
  91. }
  92. } else {
  93. // Si $fromCache es false, no intentamos recuperar datos de la caché.
  94. $this->logger->debug("No cacheamos {$endpoint}.");
  95. $queryParams['$nocache'] = true; // Forzamos una consulta nueva.
  96. }
  97. // Realizamos la solicitud HTTP GET utilizando los parámetros y encabezados proporcionados.
  98. $this->logger->debug("Get via API ...");
  99. $response = $this->httpClient->request(
  100. "GET",
  101. $endpoint,
  102. [
  103. 'query' => $queryParams, // Parámetros de consulta.
  104. 'headers' => $headers ?? $this->getHeaders($global) // Encabezados.
  105. ]
  106. );
  107. // Obtenemos el contenido de la respuesta sin procesar.
  108. $contenido = $response->getContent(false);
  109. // Intentamos decodificar el contenido: primero como Base64 y luego descomprimirlo (gzip).
  110. try {
  111. if (($contenido = base64_decode($contenido)) === false) {
  112. throw new \Exception("Error decode base 64"); // Error en la decodificación Base64.
  113. }
  114. if (($contenido = gzdecode($contenido)) === false) {
  115. throw new \Exception("Error gzdecode"); // Error al descomprimir.
  116. }
  117. } catch (\Exception $e) {
  118. // Si ocurre un error, usamos el contenido sin procesar.
  119. $contenido = $response->getContent(false);
  120. }
  121. // Si el código de estado de la respuesta no es 200 (éxito):
  122. if ($response->getStatusCode() === 200) {
  123. if ($writeCache) {
  124. $this->updateCacheHistorico($cacheResponse, $key, $endpoint, $queryParams, $global, $headers);
  125. $this->cache->save(
  126. $cacheResponse
  127. ->set(json_encode(['content' => $contenido, 'status' => $response->getStatusCode()]))
  128. ->expiresAfter($expire)
  129. );
  130. }
  131. } else {
  132. // Para otros errores, extraemos el mensaje del error (si existe) y lanzamos una excepción genérica.
  133. $error = @(@json_decode($contenido, true))["error"] ?? $contenido;
  134. if ($response->getStatusCode() == 400) return null;
  135. if ($response->getStatusCode() > 400) throw new NotAcceptableHttpException($response->getStatusCode() . "::" . $error);
  136. }
  137. // Retornamos el contenido obtenido del API
  138. return $contenido;
  139. }
  140. public function postData($endpoint, $postData, $queryParams = [])
  141. {
  142. if (!($decodedData = json_decode($postData, true))) {
  143. throw new \InvalidArgumentException();
  144. }
  145. try {
  146. $response = $this->httpClient->request(
  147. "POST",
  148. $endpoint,
  149. [
  150. 'body' => $decodedData,
  151. // necesario para hermes, hermes espera un content-type form-dat o url-conded
  152. 'headers' => $this->getHeaders(),
  153. 'query' => $queryParams,
  154. ]
  155. );
  156. } catch (\Exception $e) {
  157. throw new NotAcceptableHttpException("{$e->getMessage()} ({$e->getFile()}:{$e->getLine()})");
  158. }
  159. if ($response->getStatusCode() != 200) {
  160. $jsonError = json_decode($response->getContent(false), true);
  161. $contenido = ($jsonError and @$jsonError['error']) ? $jsonError['error'] : $response->getContent(false);
  162. throw new NotAcceptableHttpException("ENDPOINT $endpoint: $contenido");
  163. }
  164. return $response->getContent();
  165. }
  166. public function postMultiPartData($endpoint, $formFields = [])
  167. {
  168. $formFields['data']->imagen = $formFields['imagen'];
  169. $formData = new FormDataPart($formFields['data']);
  170. $headers = $formData->getPreparedHeaders()->toArray();
  171. $headers = array_merge($headers, $this->getHeaders());
  172. try {
  173. $response = $this->httpClient->request(
  174. "POST",
  175. $endpoint,
  176. [
  177. 'headers' => $headers,
  178. 'body' => $formData->bodyToIterable(),
  179. ]
  180. );
  181. } catch (\Exception $e) {
  182. }
  183. if ($response->getStatusCode() != 200) {
  184. $contenido = $response->getContent(false);
  185. throw new NotAcceptableHttpException($contenido);
  186. }
  187. return $response->getContent();
  188. }
  189. private function getHeaders($global=false)
  190. {
  191. $headers = [];
  192. if ($this->security->getUser()?->getUserToken() and !$global) {
  193. $headers['userToken'] = $this->security->getUser()->getUserToken();
  194. } else {
  195. $headers['entidadToken'] = $_ENV['WS_ENTIDAD_TOKEN'];
  196. }
  197. $headers['appToken'] = $_ENV['WS_APP_TOKEN'];
  198. return $headers;
  199. }
  200. public function updateCacheHistorico(?CacheItem $cacheResponse, string $key, $endpoint, $queryParams, $global, $headers)
  201. {
  202. $cacheHistorico = $this->cache->getItem("HISTORICO");
  203. $historico = @json_decode($cacheHistorico->get(), true) ?? [];
  204. // Si el elemento está en caché, actualizamos el historial incrementando los accesos.
  205. if ($cacheResponse and $cacheResponse->isHit() && isset($historico[$key])) {
  206. $historico[$key]["hits_historical"] += 1; // Incrementa el total de accesos históricos.
  207. $historico[$key]["hits"] += 1; // Incrementa los accesos en la sesión actual.
  208. } else {
  209. // Si no existe en el historial, añadimos una nueva entrada con los detalles de la solicitud.
  210. $historico[$key] = [
  211. "key" => $key,
  212. "hits_historical" => 1,
  213. "hits" => 0,
  214. "endpoint" => $endpoint,
  215. "queryParams" => $queryParams,
  216. "global" => $global,
  217. "expire" => $queryParams['$expire'],
  218. "headers" => $headers ?? $this->getHeaders($global),
  219. "time_expire" => time() + $queryParams['$expire'] // Fecha/hora de expiración calculada.
  220. ];
  221. }
  222. // Guardamos el historial actualizado en la caché.
  223. $this->cache->save($cacheHistorico->set(json_encode($historico)));
  224. }
  225. }