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 diagram

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>