Skip to content
Search

Error Handling

Back

By default, an exception during the read, the transformation or the load stops the pipeline. An ErrorHandler can decide for each failure instead:

Method Error carries Action other than propagate (rethrow)
onExtraction(ExtractionError): ExtractionAction cause, extractor endSource: stop the read. Rows read so far still flow, and the run completes
onTransformation(TransformationError): TransformationAction cause, transformer, rows skipBatch: drop the batch, continue with the next one
onLoading(LoadingError): LoadingAction cause, loader, rows skipLoader: the failing loader misses this batch, the remaining steps still run

A corrupted record never reaches the ErrorHandler. For more information, read Corrupted Records.

Built-in handlers:

Handler Extraction Transformation Loading
throw_error_handler() (default) propagate propagate propagate
ignore_error_handler() endSource skipBatch skipLoader
skip_rows_handler() endSource skipBatch propagate
<?php

use Flow\ETL\Foundation\Describe\{Description, Rebuild};
use Flow\ETL\Runtime\ErrorHandler;
use Flow\ETL\Runtime\ErrorHandler\{ExtractionAction, ExtractionError, LoadingAction, LoadingError, TransformationAction, TransformationError};

use function Flow\ETL\DSL\{data_frame, from_array, ignore_error_handler, to_stream};

final class SkipBrokenBatches implements ErrorHandler
{
    public static function fromDescription(array $data, Rebuild $rebuild): static
    {
        return new self();
    }

    public function describe(): ?Description
    {
        return new Description(self::class, []);
    }

    public function onExtraction(ExtractionError $error): ExtractionAction
    {
        return ExtractionAction::propagate;
    }

    public function onTransformation(TransformationError $error): TransformationAction
    {
        return TransformationAction::skipBatch;
    }

    public function onLoading(LoadingError $error): LoadingAction
    {
        return LoadingAction::propagate;
    }
}

data_frame()
    ->read(from_array([['id' => 1], ['id' => 2]]))
    ->onError(new SkipBrokenBatches())
    ->write(to_stream(__DIR__ . '/output.txt'))
    ->run();

data_frame()
    ->read(from_array([['id' => 1], ['id' => 2]]))
    ->onError(ignore_error_handler())
    ->write(to_stream(__DIR__ . '/output.txt'))
    ->run();

Tolerating invalid values per function

A scalar function that gets a value that it cannot process throws EvaluationException (an InvalidArgumentException) with the failing row of the batch. An example is ref('text')->upper() over a null value: ToUpper function requires non-null value (row 1). The original exception is getPrevious(). To accept the failure for one function, and not for the full pipeline, wrap the function in optional(). It evaluates the wrapped function. For a function that throws, it returns null:

<?php

use function Flow\ETL\DSL\{data_frame, from_array, optional, ref, to_stream};

data_frame()
    ->read(from_array([
        ['text' => 'hello'],
        ['text' => null],
    ]))
    ->withColumn('upper', optional(ref('text')->upper()))
    ->write(to_stream(__DIR__ . '/output.csv', truncate: false))
    ->run();

// Rows with a null 'text' get a null 'upper' column instead of stopping the pipeline.

Row-level Error Handling

For detailed error handling during row processing operations:

<?php

use Flow\ETL\Foundation\Exception\InvalidArgumentException;
use Flow\ETL\Values\Rows;

use function Flow\ETL\DSL\{data_frame, from_array};

function validateAndProcess(array $row): void { /* your code */ }
function logInvalidRow(array $row, string $message): void { /* your code */ }
function logGeneralError(array $row, Throwable $error): void { /* your code */ }

$successCount = 0;
$errorCount = 0;

data_frame()
    ->read(from_array([['id' => 1], ['id' => 2]]))
    ->forEach(function(Rows $rows) use (&$successCount, &$errorCount) {
        foreach ($rows->toArray() as $row) {
            try {
                validateAndProcess($row);
                $successCount++;
            } catch (InvalidArgumentException $e) {
                logInvalidRow($row, $e->getMessage());
                $errorCount++;
            } catch (Exception $e) {
                logGeneralError($row, $e);
                $errorCount++;
            }
        }
    });

echo "Success: {$successCount}, Errors: {$errorCount}";

Best Practice: For unreliable data sources, implement row-level error handling. It prevents failures of the full pipeline and gives detailed error reports.

Found a typo or an outdated section? Edit this page on GitHub


Contributors

Built in the open.

Join us on GitHub
scroll back to top