#
Exceptions
Laravel Ingest provides specific exception classes for different error scenarios. Understanding these exceptions helps you implement proper error handling in your application.
#
Exception Overview
#
SourceException
Thrown when there's a problem reading data from a source (file not found, connection failed, etc.).
use LaravelIngest\Exceptions\SourceException;
try {
$run = Ingest::start('product-importer', 'missing-file.csv');
} catch (SourceException $e) {
Log::error('Source error: ' . $e->getMessage());
// Handle: file not found, FTP connection failed, URL unreachable, etc.
}
Common causes:
- File does not exist at specified path
- FTP/SFTP connection or authentication failed
- URL is unreachable or returned an error
- Insufficient permissions to read file
#
DefinitionNotFoundException
Thrown when attempting to use an importer that hasn't been registered in config/ingest.php.
use LaravelIngest\Exceptions\DefinitionNotFoundException;
try {
$run = Ingest::start('non-existent-importer');
} catch (DefinitionNotFoundException $e) {
// "No importer found with the slug 'non-existent-importer'.
// Please check your spelling or run 'php artisan ingest:list'
// to see available importers."
}
Solution: Ensure the importer is registered:
// config/ingest.php
'importers' => [
'product-importer' => App\Ingest\ProductImporter::class,
],
#
InvalidConfigurationException
Thrown when an IngestConfig is invalid or missing required settings.
use LaravelIngest\Exceptions\InvalidConfigurationException;
try {
$run = Ingest::start('misconfigured-importer');
} catch (InvalidConfigurationException $e) {
Log::error('Config error: ' . $e->getMessage());
}
Common causes:
- Missing
for()model class - Missing
fromSource()definition - Invalid source type options
- Incompatible configuration combinations
#
ConcurrencyException
Thrown when concurrent operations conflict with each other. Includes factory methods for specific scenarios.
use LaravelIngest\Exceptions\ConcurrencyException;
try {
$run = Ingest::retry($originalRun);
} catch (ConcurrencyException $e) {
// Another retry is already in progress
}
#
Factory Methods
// Thrown when a retry is already in progress for the same run
ConcurrencyException::duplicateRetryAttempt(int $originalRunId)
// "A retry attempt for run {id} is already in progress or completed."
// Thrown when a lock cannot be acquired within the timeout
ConcurrencyException::lockTimeout(int $runId, int $timeout)
// "Could not acquire lock for run {id} within {timeout} seconds."
// Thrown on optimistic locking conflicts
ConcurrencyException::conflictingUpdate(int $runId, ?Throwable $previous)
// "Conflicting update detected for run {id}"
Handling:
try {
$run = Ingest::retry($originalRun);
} catch (ConcurrencyException $e) {
if (str_contains($e->getMessage(), 'already in progress')) {
return response()->json(['error' => 'A retry is already running'], 409);
}
throw $e;
}
#
NoFailedRowsException
Thrown when attempting to retry an import run that has no failed rows.
use LaravelIngest\Exceptions\NoFailedRowsException;
try {
$run = Ingest::retry($originalRun);
} catch (NoFailedRowsException $e) {
// "The original run has no failed rows to retry."
}
Solution: Check for failed rows before retrying:
if ($originalRun->failed_rows > 0) {
$run = Ingest::retry($originalRun);
} else {
return response()->json(['message' => 'No failed rows to retry']);
}
#
FileProcessingException
Thrown during file validation and processing. Includes factory methods for specific file-related errors.
use LaravelIngest\Exceptions\FileProcessingException;
try {
$run = Ingest::start('importer', $uploadedFile);
} catch (FileProcessingException $e) {
return response()->json(['error' => $e->getMessage()], 422);
}
#
Factory Methods
// File exceeds maximum allowed size
FileProcessingException::fileTooLarge(int $size, int $maxSize)
// "File size ({size} bytes) exceeds maximum allowed size ({maxSize} bytes)."
// File MIME type not in allowed list
FileProcessingException::invalidMimeType(string $mimeType, array $allowed)
// "File type '{mimeType}' is not allowed. Allowed types: text/csv, ..."
// Potentially malicious content detected
FileProcessingException::maliciousContent()
// "File contains potentially malicious content."
// File cannot be read
FileProcessingException::unreadableFile(string $path, ?Throwable $previous)
// "Unable to read file at path: {path}"
// File is corrupted or invalid format
FileProcessingException::corruptedFile(string $path, ?Throwable $previous)
// User-friendly message about corrupted file
Handling upload errors:
use LaravelIngest\Exceptions\FileProcessingException;
try {
$run = Ingest::start('csv-importer', $request->file('import'));
} catch (FileProcessingException $e) {
$message = $e->getMessage();
if (str_contains($message, 'too large')) {
return back()->withErrors(['file' => 'File is too large. Maximum size is 50MB.']);
}
if (str_contains($message, 'not allowed')) {
return back()->withErrors(['file' => 'Please upload a CSV or Excel file.']);
}
return back()->withErrors(['file' => 'Could not process the file.']);
}
#
Global Exception Handling
You can handle Ingest exceptions globally in your exception handler:
// app/Exceptions/Handler.php (Laravel 10 and earlier)
// or bootstrap/app.php (Laravel 11+)
use LaravelIngest\Exceptions\DefinitionNotFoundException;
use LaravelIngest\Exceptions\FileProcessingException;
use LaravelIngest\Exceptions\ConcurrencyException;
// Laravel 11+
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (DefinitionNotFoundException $e) {
return response()->json([
'error' => 'Importer not found',
'message' => $e->getMessage(),
], 404);
});
$exceptions->render(function (FileProcessingException $e) {
return response()->json([
'error' => 'File processing failed',
'message' => $e->getMessage(),
], 422);
});
$exceptions->render(function (ConcurrencyException $e) {
return response()->json([
'error' => 'Operation conflict',
'message' => $e->getMessage(),
], 409);
});
})
#
Exception Inheritance
All Ingest exceptions extend PHP's base Exception class:
Exception
├── SourceException
├── DefinitionNotFoundException
├── InvalidConfigurationException
├── ConcurrencyException
├── NoFailedRowsException
└── FileProcessingException
This allows you to catch all Ingest exceptions with a single catch block if needed:
use LaravelIngest\Exceptions\{
SourceException,
DefinitionNotFoundException,
InvalidConfigurationException,
ConcurrencyException,
NoFailedRowsException,
FileProcessingException,
};
try {
$run = Ingest::start($slug, $payload);
} catch (DefinitionNotFoundException $e) {
// Handle missing importer
} catch (FileProcessingException $e) {
// Handle file issues
} catch (SourceException $e) {
// Handle source reading issues
} catch (\Exception $e) {
// Handle any other exception
}