Pular para o conteúdo principal
ImmutableLog logo
VoltarPHP

PHP

Guia completo de integração com o ImmutableLog em PHP. Middleware automático para Laravel e Symfony — ou um cliente HTTP direto com Guzzle para workers, comandos e jobs de fila. Use o middleware terminável (Laravel) ou kernel.terminate (Symfony) para enviar depois da resposta.

Cliente HTTP direto (Guzzle)

Use a classe ImmutableLog para enviar eventos diretamente sem depender de um framework web. Ideal para comandos artisan/console, workers de fila e qualquer script PHP que precise registrar eventos.

php
<?php
// src/ImmutableLog.php
// composer require guzzlehttp/guzzle ramsey/uuid

use GuzzleHttp\Client;
use Ramsey\Uuid\Uuid;

final class ImmutableLog
{
    private Client $http;

    public function __construct(
        private string $apiKey,
        private string $service = 'php-service',
        private string $env = 'production',
        ?string $apiUrl = null,
    ) {
        $this->http = new Client([
            'base_uri' => $apiUrl ?? (getenv('IMTBL_URL') ?: 'https://api.immutablelog.com'),
            'timeout'  => 5,
        ]);
    }

    public function sendEvent(
        string $eventName,
        string $kind,                 // "success" | "info" | "error"
        array $payload,
        ?string $immutableTrail = null,
    ): void {
        $requestId = Uuid::uuid4()->toString();
        $trail = self::sanitizeTrail($immutableTrail);

        // Todos os valores de meta precisam ser strings.
        $meta = [
            'type'       => $kind,
            'event_name' => $eventName,
            'service'    => $this->service,
            'request_id' => $requestId,
            'env'        => $this->env,
        ];
        if ($trail !== null) {
            $meta['immutable_trail'] = $trail;
        }

        $event = [
            'payload' => json_encode($payload + ['timestamp' => gmdate('c')]),
            'meta'    => $meta,
        ];

        $headers = [
            'Authorization' => "Bearer {$this->apiKey}",
            'Content-Type'  => 'application/json',
            // Idempotency-Key e OBRIGATORIO (sem ele a API responde 400).
            'Idempotency-Key'         => "{$eventName}-{$requestId}",
            'Request-Id'              => $requestId,
            'X-Client-TZ'             => 'America/Sao_Paulo',
            'X-Client-Offset-Minutes' => '-180',
        ];
        if ($trail !== null) {
            $headers['X-Imtbl-Trail'] = $trail;
        }

        $this->http->post('/v1/events', [
            'headers' => $headers,
            'json'    => $event,
        ]);
    }

    // Normaliza o immutable_trail (trim, nao-vazio, max 256, sem ':').
    // O servidor responde 400 invalid_immutable_trail se a regra for violada.
    public static function sanitizeTrail(?string $value): ?string
    {
        if ($value === null) {
            return null;
        }
        $v = trim($value);
        if ($v === '') {
            return null;
        }
        $v = str_replace(':', '-', $v);
        if (mb_strlen($v) > 256) {
            $v = mb_substr($v, 0, 256);
        }
        return $v;
    }
}

// Uso / Usage
$log = new ImmutableLog(getenv('IMTBL_API_KEY'), 'payments-service', 'production');
$log->sendEvent('payment.approved', 'success', [
    'payment_id' => 'pay_abc123',
    'amount'     => 299.90,
], immutableTrail: 'order-7782');

PHP é síncrono por requisição. Para não bloquear o usuário, envie eventos a partir de um job de fila (Laravel Queue / Symfony Messenger) ou de um middleware terminável.

Retry com backoff exponencial

Para produção de alta disponibilidade, adicione retry com backoff exponencial. O ImmutableLog retorna 202 em sucesso e 200 em duplicata idempotente — nunca faça retry em 429 (limite mensal atingido).

php
public function sendWithRetry(
    string $eventName,
    string $kind,
    array $payload,
    ?string $trail = null,
    int $maxAttempts = 3,
): void {
    $delayMs = 500;

    for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
        try {
            $this->sendEvent($eventName, $kind, $payload, $trail);
            return;
        } catch (\GuzzleHttp\Exception\RequestException $e) {
            $status = $e->getResponse()?->getStatusCode();

            // 429 = limite mensal — nao fazer retry.
            if ($status === 429) {
                return;
            }
            if ($attempt + 1 < $maxAttempts) {
                usleep($delayMs * 1000);
                $delayMs *= 2; // 500ms -> 1s -> 2s
            }
        }
    }
}

Hash de dados sensíveis

Nunca envie dados pessoais brutos (e-mail, CPF, IP) para o ImmutableLog. Use SHA-256 para gerar um digest determinístico — rastreável sem expor o dado original.

php
<?php
// Faz o hash de dados sensiveis antes de enviar — nunca logue PII bruto.
function sha256_hex(string $data): string
{
    return hash('sha256', $data);
}

// Uso no payload do evento:
$payload = [
    'user_id'    => 'usr_123',
    'email_hash' => sha256_hex('user@example.com'),
    'ip_hash'    => sha256_hex('192.168.1.1'),
];

Esta documentação reflete o comportamento atual da API. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.