← Code-Übersicht

PdftotextExtractor.php

Pfad: src/Infrastructure/Rechnung/PdftotextExtractor.php

Ext: php

Größe: 1786 Bytes

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

<?php
declare(strict_types=1);

namespace Demo\Infrastructure\Rechnung;

use Demo\Domain\Rechnung\ExtractedInvoiceData;
use Demo\Domain\Rechnung\TextExtractionPort;
use RuntimeException;

/**
 * Extrahiert eingebetteten PDF-Text ueber Poppler.
 */
final class PdftotextExtractor implements TextExtractionPort
{
    /** Bindet den Adapter an das Poppler-Binary. */
    public function __construct(private readonly string $binary = 'pdftotext', private readonly ?InvoiceTextParser $parser = null)
    {
    }

    /** Extrahiert und parst eine PDF-Datei. */
    public function extract(string $pdfPath): ExtractedInvoiceData
    {
        if (!is_file($pdfPath)) {
            throw new RuntimeException('PDF fuer Textextraktion fehlt');
        }
        [$status, $stdout, $stderr] = $this->run([$this->binary, '-layout', $pdfPath, '-']);
        if ($status !== 0 || trim($stdout) === '') {
            throw new RuntimeException('pdftotext fehlgeschlagen: ' . trim($stderr));
        }
        return ($this->parser ?? new InvoiceTextParser())->parse($stdout, 0.85);
    }

    /**
     * @param list<string> $command Prozessargumente ohne Shell.
     * @return array{int,string,string} Exitcode, stdout, stderr.
     */
    private function run(array $command): array
    {
        $pipes = [];
        $process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
        if (!is_resource($process)) {
            throw new RuntimeException('pdftotext kann nicht gestartet werden');
        }
        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        return [proc_close($process), $stdout === false ? '' : $stdout, $stderr === false ? '' : $stderr];
    }
}

Frühere Versionen

Version Zeitpunkt Operation
27 2026-07-16 19:01:40.81513+02 UPDATE