release 1.0
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
|
||||
namespace UbsCsvTransformer;
|
||||
|
||||
use UbsCsvTransformer\CsvReader;
|
||||
use UbsCsvTransformer\CsvWriter;
|
||||
use UbsCsvTransformer\ConfigurationLoader;
|
||||
use UbsCsvTransformer\MetadataExtractor;
|
||||
use UbsCsvTransformer\ColumnTransformer;
|
||||
use UbsCsvTransformer\FireflyImporter;
|
||||
|
||||
/**
|
||||
* Orchestriert die gesamte CSV-Transformations-Pipeline
|
||||
*
|
||||
* Koordiniert alle Schritte von CSV-Einlesen über Metadaten-Extraktion
|
||||
* und Spalten-Transformation bis zur Ausgabe und optional zum Import in Firefly III.
|
||||
*
|
||||
* @property ConfigurationLoader $configLoader Verwaltet Konfiguration
|
||||
* @property CsvWriter $csvWriter Schreibt Output-CSV
|
||||
* @property MetadataExtractor $metadataExtractor Extrahiert Metadaten aus Header
|
||||
* @property ColumnTransformer $columnTransformer Transformiert Spalten
|
||||
* @property array $csvStructure CSV-Struktur-Konfiguration
|
||||
*/
|
||||
class TransformerEngine
|
||||
{
|
||||
private ConfigurationLoader $configLoader;
|
||||
private CsvWriter $csvWriter;
|
||||
private MetadataExtractor $metadataExtractor;
|
||||
private ColumnTransformer $columnTransformer;
|
||||
private array $csvStructure;
|
||||
private array $sampleData = [];
|
||||
private int $rowsProcessed = 0;
|
||||
private bool $debugMode = false;
|
||||
|
||||
/**
|
||||
* Initialisiert TransformerEngine mit Konfiguration
|
||||
*
|
||||
* Lädt alle erforderlichen Konfigurationen und initialisiert
|
||||
* die Komponenten (MetadataExtractor, ColumnTransformer, CsvWriter).
|
||||
* CsvReader wird später in transform() und validate() initialisiert mit dem Dateipfad.
|
||||
*
|
||||
* @param ConfigurationLoader $configLoader Lädt Konfigurationsdateien
|
||||
* @param bool $debugMode true = Debug-Modus aktivieren
|
||||
*
|
||||
* @throws \RuntimeException wenn erforderliche Konfigurationen fehlen
|
||||
*/
|
||||
public function __construct(ConfigurationLoader $configLoader, bool $debugMode = false)
|
||||
{
|
||||
$this->configLoader = $configLoader;
|
||||
$this->debugMode = $debugMode;
|
||||
|
||||
$config = $configLoader->getAll();
|
||||
|
||||
$this->csvStructure = $config['csvStructure'] ?? [];
|
||||
|
||||
$this->metadataExtractor = new MetadataExtractor(
|
||||
$config['metadata']['extractionRules'] ?? []
|
||||
);
|
||||
|
||||
$this->columnTransformer = new ColumnTransformer(
|
||||
$config['columnTransformations'] ?? [],
|
||||
[],
|
||||
$config['capitalizationExceptions'] ?? []
|
||||
);
|
||||
|
||||
// Bestimme Output-Dateiname aus Konfiguration
|
||||
$outputDir = $config['directories']['output'] ?? './output';
|
||||
$outputFileName = $config['csvStructure']['outputFilename'] ?? 'transformed.csv';
|
||||
$outputFile = rtrim($outputDir, '/') . '/' . $outputFileName;
|
||||
|
||||
$this->csvWriter = new CsvWriter(
|
||||
$outputFile,
|
||||
$config['csvStructure'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktiviert oder deaktiviert den Debug-Modus
|
||||
*
|
||||
* @param bool $enabled true = Debug-Modus aktiviert
|
||||
* @return void
|
||||
*/
|
||||
public function setDebugMode(bool $enabled): void
|
||||
{
|
||||
$this->debugMode = $enabled;
|
||||
if ($enabled) {
|
||||
DebugLogger::enable();
|
||||
} else {
|
||||
DebugLogger::disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformiert eine CSV-Datei
|
||||
*
|
||||
* Führt folgende Schritte durch:
|
||||
* 1. CSV-Datei einlesen mit CsvReader
|
||||
* 2. Metadaten aus Header extrahieren
|
||||
* 3. Spalten gemäß Konfiguration transformieren
|
||||
* 4. Daten in Output-CSV schreiben
|
||||
* 5. Beispiel-Daten sammeln (maximal 3 Zeilen oder maxRows)
|
||||
*
|
||||
* Der Output-Dateipfad wird aus der Konfiguration bestimmt und kann nicht überschrieben werden.
|
||||
*
|
||||
* @param string $inputFile Pfad zur Input-CSV-Datei
|
||||
* @param int $maxRows Maximale Anzahl Datenzeilen zu transformieren (0 = alle).
|
||||
* Beispiel-Daten werden begrenzt auf min(3, maxRows)
|
||||
*
|
||||
* @return array Transformations-Ergebnis mit:
|
||||
* - success: bool (true = erfolgreich, false = Fehler)
|
||||
* - inputFile: string (Input-Dateipfad, nur bei Erfolg)
|
||||
* - outputFile: string (Output-Dateipfad, nur bei Erfolg)
|
||||
* - rowsProcessed: int (tatsächlich verarbeitete Datenzeilen)
|
||||
* - sampleData: array (Erste Beispiel-Zeilen, max 3 oder maxRows)
|
||||
* - metadata: array (Extrahierte Metadaten, nur bei Erfolg)
|
||||
* - outputColumns: int (Anzahl Output-Spalten)
|
||||
* - error: string (Fehlermeldung, nur bei Fehler)
|
||||
*/
|
||||
public function transform(string $inputFile, int $maxRows = 0): array
|
||||
{
|
||||
$this->sampleData = [];
|
||||
$this->rowsProcessed = 0;
|
||||
DebugLogger::reset();
|
||||
|
||||
try {
|
||||
if ($this->debugMode) {
|
||||
DebugLogger::log('transformer', 'Transformation started', [
|
||||
'inputFile' => $inputFile,
|
||||
'maxRows' => $maxRows
|
||||
]);
|
||||
}
|
||||
|
||||
// Validiere Input-Datei
|
||||
if (!file_exists($inputFile)) {
|
||||
throw new \RuntimeException("Input-Datei nicht gefunden: {$inputFile}");
|
||||
}
|
||||
|
||||
// Initialisiere CsvReader mit Dateipfad und Konfiguration
|
||||
$csvReader = new CsvReader($inputFile, $this->csvStructure);
|
||||
|
||||
// Lese Metadaten-Zeilen (vor der Header-Zeile)
|
||||
$metadataLines = $csvReader->readMetadataLines();
|
||||
|
||||
// Extrahiere Metadaten aus den Metadaten-Zeilen
|
||||
$metadata = $this->metadataExtractor->extract($metadataLines);
|
||||
|
||||
// Initialisiere ColumnTransformer mit extrahierten Metadaten
|
||||
$this->columnTransformer = new ColumnTransformer(
|
||||
$this->configLoader->get('columnTransformations', []),
|
||||
$metadata,
|
||||
$this->configLoader->get('capitalizationExceptions', [])
|
||||
);
|
||||
|
||||
// Lese CSV-Daten mit Header-Keys als Array-Keys
|
||||
$dataRows = $csvReader->readCsvData($maxRows);
|
||||
if (empty($dataRows)) {
|
||||
throw new \RuntimeException("Keine Datenzeilen in CSV-Datei");
|
||||
}
|
||||
|
||||
// Berechne Limit für Beispiel-Daten
|
||||
$sampleLimit = $maxRows == 0 ? 3 : $maxRows;
|
||||
|
||||
// Transformiere Zeilen und sammle sie
|
||||
$transformedData = [];
|
||||
|
||||
foreach ($dataRows as $row) {
|
||||
// Prüfe ob maxRows erreicht
|
||||
if ($maxRows > 0 && $this->rowsProcessed >= $maxRows) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Transformiere Zeile
|
||||
$transformedRow = $this->columnTransformer->transformRow($row);
|
||||
$transformedData[] = $transformedRow;
|
||||
|
||||
// Speichere Beispiel-Daten
|
||||
if (count($this->sampleData) < $sampleLimit) {
|
||||
$this->sampleData[] = $transformedRow;
|
||||
}
|
||||
|
||||
$this->rowsProcessed++;
|
||||
}
|
||||
|
||||
// Entferne Spalten die aus dem Output ausgeschlossen werden sollen
|
||||
$excludeColumns = $this->csvStructure['excludeOutputColumns'] ?? [];
|
||||
if (!empty($excludeColumns)) {
|
||||
$excludeMap = array_flip($excludeColumns);
|
||||
$transformedData = array_map(
|
||||
static fn(array $row): array => array_diff_key($row, $excludeMap),
|
||||
$transformedData
|
||||
);
|
||||
$this->sampleData = array_map(
|
||||
static fn(array $row): array => array_diff_key($row, $excludeMap),
|
||||
$this->sampleData
|
||||
);
|
||||
}
|
||||
|
||||
// Schreibe alle transformierten Daten in Output-CSV
|
||||
$this->csvWriter->write($transformedData);
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'inputFile' => $inputFile,
|
||||
'outputFile' => $this->csvWriter->getOutputFile(),
|
||||
'rowsProcessed' => $this->rowsProcessed,
|
||||
'sampleData' => $this->sampleData,
|
||||
'metadata' => $metadata,
|
||||
'outputColumns' => $this->columnTransformer->getOutputColumns(),
|
||||
];
|
||||
|
||||
if ($this->debugMode) {
|
||||
$result['debug_logs'] = DebugLogger::getLogs();
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'rowsProcessed' => $this->rowsProcessed,
|
||||
'sampleData' => $this->sampleData,
|
||||
'outputColumns' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformiert und importiert CSV in Firefly III
|
||||
*
|
||||
* Führt Transformation durch und importiert die Ausgabe-Datei
|
||||
* in Firefly III wenn in der Konfiguration aktiviert.
|
||||
*
|
||||
* Rückwärts-kompatibel mit legacy Signatur.
|
||||
*
|
||||
* @param string $inputFile Pfad zur Input-CSV-Datei
|
||||
* @param int $maxRows Maximale Anzahl Datenzeilen zu verarbeiten (0 = alle)
|
||||
*
|
||||
* @return array Transformations- und Import-Ergebnis mit:
|
||||
* - success: bool (true = transformation erfolgreich)
|
||||
* - inputFile: string
|
||||
* - outputFile: string
|
||||
* - rowsProcessed: int
|
||||
* - sampleData: array
|
||||
* - metadata: array
|
||||
* - outputColumns: int
|
||||
* - import: array (Firefly Import-Ergebnis, wenn autoImport aktiv)
|
||||
* - error: string (falls Fehler)
|
||||
*/
|
||||
public function transformAndImport(string $inputFile, int $maxRows = 0): array
|
||||
{
|
||||
// Zuerst transformieren
|
||||
$transformResult = $this->transform($inputFile, $maxRows);
|
||||
|
||||
if (!$transformResult['success']) {
|
||||
return $transformResult;
|
||||
}
|
||||
|
||||
// Prüfe ob Auto-Import in Konfiguration aktiviert ist
|
||||
$fireflyConfig = $this->configLoader->get('fireflyImport', []);
|
||||
if (empty($fireflyConfig['autoImport'])) {
|
||||
return $transformResult;
|
||||
}
|
||||
|
||||
// Führe Firefly-Import durch
|
||||
try {
|
||||
$importer = new FireflyImporter($fireflyConfig);
|
||||
$importResult = $importer->import($transformResult['outputFile']);
|
||||
$transformResult['import'] = $importResult;
|
||||
|
||||
return $transformResult;
|
||||
} catch (\Exception $e) {
|
||||
$transformResult['import'] = [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
return $transformResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert eine CSV-Datei gegen die Konfiguration
|
||||
*
|
||||
* Prüft ob erforderliche Metadaten vorhanden sind
|
||||
* und ob die CSV-Struktur der Konfiguration entspricht.
|
||||
*
|
||||
* @param string $inputFile Pfad zur zu validierenden CSV-Datei
|
||||
*
|
||||
* @return array Validierungs-Ergebnis mit:
|
||||
* - valid: bool (true = Validierung erfolgreich)
|
||||
* - metadata: array (Extrahierte Metadaten, wenn valid)
|
||||
* - line_count: int (Gesamtzahl Zeilen, wenn valid)
|
||||
* - error: string (Fehlermeldung, wenn nicht valid)
|
||||
* - metadata_found: array (Gefundene Metadaten trotz Fehler)
|
||||
*/
|
||||
public function validate(string $inputFile): array
|
||||
{
|
||||
try {
|
||||
if (!file_exists($inputFile)) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => "Datei nicht gefunden: {$inputFile}",
|
||||
];
|
||||
}
|
||||
|
||||
// Initialisiere CsvReader mit Dateipfad
|
||||
$csvReader = new CsvReader($inputFile, $this->csvStructure);
|
||||
|
||||
// Extrahiere Metadaten-Zeilen (vor der Header-Zeile)
|
||||
$metadataLines = $csvReader->readMetadataLines();
|
||||
$metadata = $this->metadataExtractor->extract($metadataLines);
|
||||
|
||||
// Prüfe auf erforderliche Metadaten
|
||||
$requiredMetadata = [
|
||||
'account_iban',
|
||||
'currency_code',
|
||||
];
|
||||
|
||||
$missingMetadata = [];
|
||||
foreach ($requiredMetadata as $key) {
|
||||
if (empty($metadata[$key])) {
|
||||
$missingMetadata[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missingMetadata)) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => 'Fehlende Metadaten: ' . implode(', ', $missingMetadata),
|
||||
'metadata_found' => $metadata,
|
||||
];
|
||||
}
|
||||
|
||||
// Zähle Gesamtzahl Zeilen
|
||||
$lineCount = $csvReader->countLines();
|
||||
|
||||
return [
|
||||
'valid' => true,
|
||||
'metadata' => $metadata,
|
||||
'line_count' => $lineCount,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => 'Validierungs-Fehler: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die gesammelten Beispiel-Daten zurück
|
||||
*
|
||||
* @return array Beispiel-Daten (maximal 3 oder maxRows Zeilen)
|
||||
*/
|
||||
public function getSampleData(): array
|
||||
{
|
||||
return $this->sampleData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Anzahl verarbeiteter Datenzeilen zurück
|
||||
*
|
||||
* @return int Anzahl transformierter Zeilen
|
||||
*/
|
||||
public function getRowsProcessed(): int
|
||||
{
|
||||
return $this->rowsProcessed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user