← Code-Übersicht

DateBasedInvoiceStorage.php

Pfad: src/Infrastructure/Rechnung/DateBasedInvoiceStorage.php

Ext: php

Größe: 2789 Bytes

Geändert: 2026-07-16 16:57:10+02

<?php
declare(strict_types=1);

namespace Demo\Infrastructure\Rechnung;

use DateTimeImmutable;
use Demo\Domain\Rechnung\InvoiceFileStoragePort;
use RuntimeException;

/**
 * Datumsbasierte, hashgepruefte Ablage fuer Original-PDFs.
 */
final class DateBasedInvoiceStorage implements InvoiceFileStoragePort
{
    /** Bindet die Ablage an ihr Basisverzeichnis. */
    public function __construct(private readonly string $basePath)
    {
    }

    /** Baut einen stabilen Zielpfad aus Datum, Lieferant und Quell-ID. */
    public function buildTargetPath(?string $invoiceDateHint, string $vendorName, string $sourceId): string
    {
        $date = $invoiceDateHint === null ? false : DateTimeImmutable::createFromFormat('!Y-m-d', $invoiceDateHint);
        $date = $date === false ? new DateTimeImmutable('now') : $date;
        $vendor = preg_replace('/[^a-z0-9]+/', '-', strtolower($vendorName));
        $vendor = trim($vendor ?? '', '-') ?: 'unknown';
        return rtrim($this->basePath, '/') . '/' . $date->format('Y/m') . '/' . $date->format('Y-m-d') . '_' . $vendor . '_' . substr(hash('sha256', $sourceId), 0, 12) . '.pdf';
    }

    /** Persistiert nur valide PDFs und erkennt Zielkonflikte. */
    public function persistDownloadedPdf(string $pdfBinary, string $targetPath): void
    {
        if (!str_starts_with($pdfBinary, '%PDF-') || strlen($pdfBinary) < 100 || strlen($pdfBinary) > 5_000_000) {
            throw new RuntimeException('Download ist kein gueltiges PDF oder hat unzulaessige Groesse');
        }
        $incomingHash = hash('sha256', $pdfBinary);
        if (is_file($targetPath)) {
            if ($this->sha256($targetPath) === $incomingHash) {
                return;
            }
            throw new RuntimeException('Zielpfad enthaelt bereits ein anderes Dokument');
        }
        $directory = dirname($targetPath);
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new RuntimeException('Rechnungsverzeichnis kann nicht erstellt werden');
        }
        $temporary = tempnam($directory, '.invoice-');
        if ($temporary === false || file_put_contents($temporary, $pdfBinary, LOCK_EX) !== strlen($pdfBinary) || !rename($temporary, $targetPath)) {
            if (is_string($temporary)) {
                @unlink($temporary);
            }
            throw new RuntimeException('PDF kann nicht atomar persistiert werden');
        }
    }

    /** Berechnet den SHA-256 eines gespeicherten Originals. */
    public function sha256(string $path): string
    {
        $hash = is_file($path) ? hash_file('sha256', $path) : false;
        if ($hash === false) {
            throw new RuntimeException('SHA-256 kann nicht berechnet werden');
        }
        return $hash;
    }
}

Frühere Versionen

Version Zeitpunkt Operation
25 2026-07-16 19:01:40.814451+02 UPDATE