Skip to content

Commit

Permalink
Merge pull request #46 from eclipxe13/version-0.5.5
Browse files Browse the repository at this point in the history
Mejorar el manejo de registros de SoapCaller (version 0.5.5)
  • Loading branch information
eclipxe13 authored May 24, 2024
2 parents 393a049 + 7d11e00 commit aac461a
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 28 deletions.
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,31 +239,50 @@ para poder revisar el problema sobre la información enviada.
Esta librería genera mensajes utilizando *PSR-3: Logger Interface*, y se utiliza dentro del objeto `SoapFactory`
para crear un `SoapCaller`. Este objeto envía dos tipos de mensajes: `LogLevel::ERROR` cuando ocurre un error al
momento de establecer comunicación con los servicios, y `LogLevel::DEBUG` cuando se ejecutó una llamada SOAP.
Ambos mensajes están representados como una cadena en formato JSON, por lo que, para leerla correctamente
Ambos mensajes están representados como una cadena en formato JSON, por lo que, para leerla fácilmente
es importante decodificarla.

La clase [`PhpCfdi\Finkok\Tests\LoggerPrinter`](https://github.com/phpcfdi/finkok/blob/main/tests/LoggerPrinter.php)
es un *ejemplo de implementación* de `LoggerInterface` que manda los mensajes recibidos a la salida estándar o
a un archivo. Es importante notar que el objeto `LoggerPrinter` no está disponible en el paquete, sin embargo,
lo puedes descargar y poner dentro de tu proyecto con tu espacio de nombres.
El formato JSON es mejor dado que permite analizar el texto y encontrar caracteres especiales,
mientras que, al convertirlo a un texto más entendible para el humano, estos caracteres especiales
se pueden esconder o interpretar de forma errónea.

De igual forma, se puede utilizar cualquier objeto que implemente `LoggerInterface`, por ejemplo, en Laravel se
puede usar `$logger = app(Psr\Log\LoggerInterface::class)`. Pero recuerda que, una vez que tengas el mensaje,
deberás decodificarlo de JSON a texto plano.
Se ofrece la clase `PhpCfdi\Finkok\Helpers\FileLogger` como una utilería de `LoggerInterface`
que manda los mensajes recibidos a la salida estándar o a un archivo.

Para establecer el objeto `Logger` es recomendable hacerlo de la siguiente forma:
También se ofrece la clase `PhpCfdi\Finkok\Helpers\JsonDecoderLogger` como una utilería de `LoggerInterface`
que decodifica el mensaje JSON y luego lo convierte a cadena de caracteres usando la función `print_r()`,
para después mandarlo a otro objeto `LoggerInterface`.

En el siguiente ejemplo se muestra la forma recomendada para establecer el objeto `Logger`,
también se muestra el uso de `JsonDecoderLogger` para realizar la conversión de JSON a texto plano y
`FileLogger` para enviar el mensaje a un archivo específico.

La clase `JsonDecoderLogger` puede generar pérdida de información, pero los mensajes son más entendibles,
si deseas también incluir el mensaje JSON puedes usar `JsonDecoderLogger::setAlsoLogJsonMessage(true)`.

```php
use PhpCfdi\Finkok\FinkokEnvironment;
use PhpCfdi\Finkok\FinkokSettings;
use PhpCfdi\Finkok\Tests\LoggerPrinter;
use PhpCfdi\Finkok\Helpers\FileLogger;
use PhpCfdi\Finkok\Helpers\JsonDecoderLogger;

$logger = new LoggerPrinter('/tmp/finkok.log');
$logger = new JsonDecoderLogger(new FileLogger('/tmp/finkok.log'));
$logger->setAlsoLogJsonMessage(true); // enviar en texto simple y también en formato JSON

$settings = new FinkokSettings('[email protected]', 'secret', FinkokEnvironment::makeProduction());
$settings->soapFactory()->setLogger($logger);
```

Si estás usando Laravel, ya cuentas con una implementación de `LoggerInterface`, por lo que te recomiendo usar:

```php
/** @var \Psr\Log\LoggerInterface $logger */
$logger = app(\Psr\Log\LoggerInterface::class);

// Encapsular el logger en el decodificador JSON:
$logger = new \PhpCfdi\Finkok\Helpers\JsonDecoderLogger($logger);
```

## Compatibilidad

Esta librería se mantendrá compatible con al menos la versión con
Expand Down
12 changes: 12 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ Nos apegamos a [SEMVER](SEMVER.md), revisa la información para entender mejor e

Estos cambios se aplican y se publican, pero aún no son parte de una versión liberada.

## Versión 0.5.5 2024-05-24

Se mueve `PhpCfdi\Finkok\Tests\LoggerPrinter` a `PhpCfdi\Finkok\Helpers\FileLogger` para permitir la distribución
de la herramienta dentro de la librería.

Se crea la utilería `PhpCfdi\Finkok\Helpers\JsonDecoderLogger` para transformar un mensaje JSON a texto simple
generado por la función `print_r`. Se puede configurar para enviar también el mensaje JSON.

Se normaliza el formato de los mensajes JSON para usar `JSON_PRETTY_PRINT` y `JSON_UNESCAPED_SLASHES`.

Se actualiza la documentación en el `README`.

## Versión 0.5.4 2024-04-12

Se actualiza el año de la licencia a 2024.
Expand Down
3 changes: 3 additions & 0 deletions docs/TODO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# phpcfdi/finkok To Do List

- Modificar la clase `SoapCaller` para que no dependa de `LoggerInterface` y en su lugar introducir
una interfaz para capturar los eventos de llamada exitosa y llamada con error.

- Agregar la ejecución de test de integración al flujo de trabajo `.github/workflows/build.yml`;
es necesario entender cómo funcionan los secretos para poder crear un archivo de entorno seguro.

Expand Down
10 changes: 3 additions & 7 deletions tests/LoggerPrinter.php → src/Helpers/FileLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

declare(strict_types=1);

namespace PhpCfdi\Finkok\Tests;
namespace PhpCfdi\Finkok\Helpers;

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

final class LoggerPrinter extends AbstractLogger implements LoggerInterface
final class FileLogger extends AbstractLogger implements LoggerInterface
{
/** @var string */
public $outputFile;
Expand All @@ -24,10 +24,6 @@ public function __construct(string $outputFile = 'php://stdout')
*/
public function log($level, $message, array $context = []): void
{
file_put_contents(
$this->outputFile,
PHP_EOL . print_r(json_decode(strval($message)), true),
FILE_APPEND
);
file_put_contents($this->outputFile, $message . PHP_EOL, FILE_APPEND);
}
}
119 changes: 119 additions & 0 deletions src/Helpers/JsonDecoderLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\Finkok\Helpers;

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

/**
* Esta clase es un adaptador para convertir un mensaje de registro (log) que está
* en formato Json y es decodificado y convertido en texto a través de la función
* print_r, luego pasa el mensaje al logger con el que fue construido el objeto.
*
* Si el mensaje no es un Json no válido entonces pasa sin convertirse.
*
* Tiene algunas opciones:
* - alsoLogJsonMessage: Envía los dos mensajes, tanto el texto como el json al logger.
* - useJsonValidateIfAvailable: Usa \json_validate() si está disponible.
*/
final class JsonDecoderLogger extends AbstractLogger implements LoggerInterface
{
/** @var LoggerInterface */
private $logger;

/** @var bool */
private $useJsonValidateIfAvailable = true;

/** @var bool */
private $alsoLogJsonMessage = false;

/** @var bool */
private $lastMessageWasJsonValid = false;

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

/**
* Define si se utilizará la función \json_validate en caso de estar disponible.
*
* @param bool|null $value El nuevo estado, si se establece NULL entonces solo devuelve el espado previo.
* @return bool El estado previo
*/
public function setUseJsonValidateIfAvailable(bool $value = null): bool
{
$previous = $this->useJsonValidateIfAvailable;
if (null !== $value) {
$this->useJsonValidateIfAvailable = $value;
}
return $previous;
}

/**
* Define si también se mandará el mensaje JSON al Logger.
*
* @param bool|null $value El nuevo estado, si se establece NULL entonces solo devuelve el espado previo.
* @return bool El estado previo
*/
public function setAlsoLogJsonMessage(bool $value = null): bool
{
$previous = $this->alsoLogJsonMessage;
if (null !== $value) {
$this->alsoLogJsonMessage = $value;
}
return $previous;
}

public function lastMessageWasJsonValid(): bool
{
return $this->lastMessageWasJsonValid;
}

/**
* @inheritDoc
* @param string|\Stringable $message
* @param mixed[] $context
*/
public function log($level, $message, array $context = []): void
{
$this->logger->log($level, $this->jsonDecode($message), $context);
if ($this->lastMessageWasJsonValid && $this->alsoLogJsonMessage) {
$this->logger->log($level, $message, $context);
}
}

/** @param string|\Stringable $string */
private function jsonDecode($string): string
{
$this->lastMessageWasJsonValid = false;
$string = strval($string);

// json_validate and json_decode
if ($this->useJsonValidateIfAvailable && function_exists('\json_validate')) {
if (\json_validate($string)) {
$this->lastMessageWasJsonValid = true;
return $this->varDump(json_decode($string));
}

return $string;
}

// json_decode only
$decoded = json_decode($string);
if (JSON_ERROR_NONE === json_last_error()) {
$this->lastMessageWasJsonValid = true;
return $this->varDump($decoded);
}

return $string;
}

/** @param mixed $var */
private function varDump($var): string
{
return print_r($var, true);
}
}
18 changes: 11 additions & 7 deletions src/SoapCaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function call(string $methodName, array $parameters): stdClass
$result = $soap->__soapCall($methodName, [$finalParameters]);
$this->logger->debug(strval(json_encode([
$methodName => $this->extractSoapClientTrace($soap),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)));
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)));
/** @var stdClass $result */
return $result;
} catch (Throwable $exception) {
Expand All @@ -69,24 +69,28 @@ public function call(string $methodName, array $parameters): stdClass
$this->extractSoapClientTrace($soap),
['exception' => ($exception instanceof JsonSerializable) ? $exception : print_r($exception, true)]
),
JSON_PRETTY_PRINT
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
)));
throw new RuntimeException(sprintf('Fail soap call to %s', $methodName), 0, $exception);
}
}

/**
* @param SoapClient $soapClient
* @return array<string, string>
* @return array<string, array<string, string>>
* @noinspection PhpUsageOfSilenceOperatorInspection
*/
protected function extractSoapClientTrace(SoapClient $soapClient): array
{
return [
'request.headers' => (string) @$soapClient->__getLastRequestHeaders(),
'request.body' => (string) @$soapClient->__getLastRequest(),
'response.headers' => (string) @$soapClient->__getLastResponseHeaders(),
'response.body' => (string) @$soapClient->__getLastResponse(),
'request' => [
'headers' => (string) @$soapClient->__getLastRequestHeaders(),
'body' => (string) @$soapClient->__getLastRequest(),
],
'response' => [
'headers' => (string) @$soapClient->__getLastResponseHeaders(),
'body' => (string) @$soapClient->__getLastResponse(),
],
];
}

Expand Down
5 changes: 4 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use PhpCfdi\Credentials\Credential;
use PhpCfdi\Finkok\FinkokEnvironment;
use PhpCfdi\Finkok\FinkokSettings;
use PhpCfdi\Finkok\Helpers\FileLogger;
use PhpCfdi\Finkok\Helpers\JsonDecoderLogger;
use PhpCfdi\Finkok\SoapFactory;

abstract class TestCase extends \PHPUnit\Framework\TestCase
Expand All @@ -31,7 +33,8 @@ public function createSettingsFromEnvironment(SoapFactory $soapFactory = null):
$this->getName(),
uniqid()
);
$settings->soapFactory()->setLogger(new LoggerPrinter($loggerOutputFile));
$logger = new JsonDecoderLogger(new FileLogger($loggerOutputFile));
$settings->soapFactory()->setLogger($logger);
}
return $settings;
}
Expand Down
Loading

0 comments on commit aac461a

Please sign in to comment.