← Code-Übersicht

CurlLexwareTransport.php

Pfad: src/Infrastructure/Rechnung/CurlLexwareTransport.php

Ext: php

Größe: 3376 Bytes

Geändert: 2026-07-16 17:37:59+02

<?php
declare(strict_types=1);

namespace Demo\Infrastructure\Rechnung;

use CURLFile;
use RuntimeException;

final class CurlLexwareTransport implements LexwareTransportPort
{
    private float $lastRequestAt = 0.0;

    public function __construct(
        private readonly string $baseUrl,
        private readonly string $apiKey,
        private readonly int $rateLimitPerSecond = 2,
    ) {
        if ($this->apiKey === '') {
            throw new RuntimeException('Lexware API-Key fehlt');
        }
    }

    public function request(
        string $method,
        string $path,
        array $query = [],
        ?array $payload = null,
        ?string $filePath = null,
    ): array {
        $this->throttle();
        $url = $this->baseUrl . $path;
        if ($query !== []) {
            $url .= '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
        }
        $headers = ['Authorization: Bearer ' . $this->apiKey, 'Accept: application/json'];
        $body = null;
        if ($filePath !== null) {
            $body = ['file' => new CURLFile($filePath, 'application/pdf', basename($filePath))];
        } elseif ($payload !== null) {
            $body = json_encode($payload, JSON_THROW_ON_ERROR);
            $headers[] = 'Content-Type: application/json';
        }
        $curl = curl_init($url);
        if ($curl === false) {
            throw new RuntimeException('Lexware HTTP-Client kann nicht initialisiert werden');
        }
        $method = strtoupper($method);
        if (!in_array($method, ['GET', 'POST'], true)) {
            throw new RuntimeException('Nicht erlaubte Lexware-HTTP-Methode');
        }
        $configured = curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        if (!$configured || ($method === 'POST' && !curl_setopt($curl, CURLOPT_POST, true))) {
            throw new RuntimeException('Lexware HTTP-Client kann nicht konfiguriert werden');
        }
        if ($body !== null && !curl_setopt($curl, CURLOPT_POSTFIELDS, $body)) {
            throw new RuntimeException('Lexware HTTP-Payload kann nicht gesetzt werden');
        }
        $response = curl_exec($curl);
        $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
        $errorNumber = curl_errno($curl);
        if (!is_string($response)) {
            throw new RuntimeException('Lexware Transportfehler ' . $errorNumber);
        }
        if ($status < 200 || $status >= 300) {
            throw new RuntimeException('Lexware HTTP-Fehler ' . $status);
        }
        if ($response === '') {
            return [];
        }
        $decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) {
            throw new RuntimeException('Lexware-Antwort ist kein JSON-Objekt');
        }
        return $decoded;
    }

    private function throttle(): void
    {
        $minimum = 1 / max(1, $this->rateLimitPerSecond);
        $remaining = $minimum - (microtime(true) - $this->lastRequestAt);
        if ($remaining > 0) {
            usleep((int) ceil($remaining * 1_000_000));
        }
        $this->lastRequestAt = microtime(true);
    }
}