Importieren einer CSV-Datei

In diesem Beispiel geht es um einen Workflow zum Import von CSV-Dateien.

Zuerst sollen in einem Verzeichnis nacheinander alle *.csvDateien eingelesen und im Anschluß jede Zeile der eingelesenen Datei in ein anderes Verzeichnis abgelegt werden.

Der Workflow sieht folgendermassen aus:

uml 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" title="" />

Zum Schutz vor der Verarbeitung, wird die Datei im Schritt readfile nach der Identifizierung umbenannt.

Im Schritt archived wird die Importdatei gelöscht.

Eine Archivierung der CSV-Datei kann optional hinzugefügt werden.

Container definieren

Als erstes wird eine Container-Klasse definiert. Ein Objekt dieser Klasse definiert den Status und enthält alle Informationen.

class Import extends \Alvine\Core\Alvine implements \Alvine\Application\Workflow\Container {

    use \Alvine\Application\Workflow\Container\Implementation;
}

Aktion definieren

Als nächstes muss die eigentliche Aktion definiert werden. Hier soll für jede Zeile in der CSV eine eigene Datei in ein anderes Verzeichnis /tmp/separat/ geschrieben werden.

Alternativ können die Daten auch direkt in eine Datenbank geschrieben oder andersweitig verarbeitet werden.

class SeparateAction extends \Alvine\Core\Alvine 
    implements \Alvine\Application\Workflow\Action {

    public function run
        (\Alvine\Application\Workflow\Container $container):
                  \Alvine\Application\Workflow\Container {

        /** Ausgabeverzeichnis definieren und falls nicht vorhanden anlegen */
        $dir=\Alvine\IO\File\TemporaryDirectory::getSystemDirectory()
            ->addChild('separat');
        $dir->create();

        /** Alle Einträge durchgehen */
        foreach($container->rows AS $i=> $row) {
            $j=0;

            /** Verfügbaren Dateinamen ermitteln */
            while(true) {
                $fn='out-'.$i.'-'.$j++.'.csv';
                $file=new \Alvine\IO\File\File($fn, $dir);
                if(!$file->exists()) break;
            }

            /** Datei anlegen */
            (new \Alvine\IO\CsvWriter($file->getOutputStream()))
                ->writeRow($row);
        }


        return $container;
    }

    public static function getInstanceFromParameterMap
        (\Alvine\Types\Map\ParameterMap $data):
    \Alvine\Application\Workflow\Action {
        return new static();
    }

}

XML einlesen und Workflow erstellen

Die XML wird eingelesen und verarbeitet.

$processor=(new \Alvine\Application\Workflow\Parser\XMLParser)->parse($xml);

Das Ergebnis ist eine Instanzen des Processors.

Einen neuen Container erstellen

Über die Factory-Methode wird ein neuer Container erstellt.

$container=$processor->create();

Workflow Automatisierung

Die Definition der Automatisierung sorgt dafür, das der gesamte Workflow durchlaufen wird.

Ausgabe der Containerinformationen

echo (string) $container;

Das Ergebnis des Workflows ist ein Container im Status archived.

ID        : 62f96686-b310-4d15-8a45-f51a5272eda0
State     : archived
roundtrip : 3

⬤  2019-06-01 10:00:00  import-csv     
        ▶ container created
▷  2019-06-01 10:00:00  import-csv             ⬤ ▬▶ created        
        ▶ container state changed from  to created
⬗  2019-06-01 10:00:00  import-csv     
        ▶ importe file /tmp/import/a4.csv.
▷  2019-06-01 10:00:00  import-csv         created ▬▶ readfile       
        ▶ container state changed from created to readfile
▷  2019-06-01 10:00:00  import-csv        readfile ▬▶ separated      
        ▶ container state changed from readfile to separated
⬗  2019-06-01 10:00:00  import-csv     
        ▶ imported file /tmp/import/11cef568-fc73-4529-e195-5b4a7d2ce7e0 deleted.
▷  2019-06-01 10:00:00  import-csv       separated ▬▶ archived       
        ▶ container state changed from separated to archived 

Man sieht in dem Beispiel gut die einzelnen Übergänge und Aktionen.

XML-Datei

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Definition des Workflows -->
<workflow name="import-csv">

    <admission state="created" 
               container-class="\MyNamespace\Import" />

    <states> 
        <state name="created" />
        <state name="readfile" />
        <state name="separated" />
        <state name="archived" />
    </states> 

    <steps> 
        <step name="readfile"> 
            <actions>
                <!-- Daten einlesen -->
                <action class="\Alvine\Application\Workflow\Action\File\ImportCsv">
                    <parameters>
                        <parameter 
                            name="path">/tmp/import/</parameter>
                        <parameter 
                            name="extension">csv</parameter>
                    </parameters>
                </action>
            </actions>
        </step>
        <step name="separate"> 
            <actions>
                <!-- Einzelne Datensätze separieren -->
                <action class="\MyNamespace\SeparateAction" />
            </actions>
        </step>
        <step name="archive"> 
            <actions>
                <action class="\Alvine\Application\Workflow\Action\File\DeleteImported" />
            </actions>
        </step>

    </steps>

    <automation>
        <rules>
            <!-- Wurde ein leerer Container erstellt, so soll er 
            in den Status readfile überführt werden -->
            <rule name="created" 
                  on="\Alvine\Application\Workflow\Event\Created">
                <actions>
                    <action class="\Alvine\Application\Workflow\Automation\Action\DoTransition">
                        <parameters>
                            <parameter 
                                name="transition">createdToReadfile</parameter>
                        </parameters>
                    </action>
                </actions>
            </rule>            
            <rule name="handle-transistion-1" 
                  on="\Alvine\Application\Workflow\Event\EndTransition">
                <conditions>
                    <condition class="\Alvine\Application\Workflow\Automation\Condition\IsState">
                        <parameters>
                            <parameter 
                                name="state">readfile</parameter>
                        </parameters>
                    </condition>
                </conditions>
                <actions>
                    <action class="\Alvine\Application\Workflow\Automation\Action\DoTransition">
                        <parameters>
                            <parameter 
                                name="transition">readfileToSeparated</parameter>
                        </parameters>
                    </action>
                </actions>
            </rule>            
            <rule name="handle-transistion-2" 
                  on="\Alvine\Application\Workflow\Event\EndTransition">
                <conditions>
                    <condition 
                        class="\Alvine\Application\Workflow\Automation\Condition\IsState">
                        <parameters>
                            <parameter 
                                name="state">separated</parameter>
                        </parameters>
                    </condition>
                </conditions>
                <actions>
                    <action class="\Alvine\Application\Workflow\Automation\Action\DoTransition">
                        <parameters>
                            <parameter 
                                name="transition">separatedToArchived</parameter>
                        </parameters>
                    </action>
                </actions>
            </rule>            
        </rules>
    </automation>    

    <transitions>
        <transition name="createdToReadfile" 
                    from="created" 
                    to="readfile" 
                    with="readfile" />
        <transition name="readfileToSeparated" 
                    from="readfile" 
                    to="separated" 
                    with="separate" />
        <transition name="separatedToArchived" 
                    from="separated" 
                    to="archived" 
                    with="archive" />
    </transitions>

</workflow>