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 |