Skip to content

Error handling

  • Parser::parseFile() throws a RuntimeException when the file does not exist or cannot be read
  • Parser::parse() and Parser::parseFile() throw a Silarhi\Qif\Exception\ParseException on a value they cannot read without guessing: a date that does not exist or is not a date, an amount that is not a number or is ambiguous (1,234), a split without amount
  • Writer::saveToFile() throws a RuntimeException when the directory is not writable or the file cannot be opened
  • Transaction::setAmount() and addSplit() throw an InvalidArgumentException when given a string that is not an amount or is ambiguous ('1,234')

The messages include the path or the value, as written in the file, and the parse errors start with where they happened:

errors.php
<?php
use Silarhi\Qif\Enums\Types;
use Silarhi\Qif\Exception\ParseException;
use Silarhi\Qif\Parser;
use Silarhi\Qif\Transaction;
use Silarhi\Qif\Writer;
try {
Parser::parseFile('missing.qif');
} catch (RuntimeException $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
(new Writer())->saveToFile('/missing/directory/export.qif');
} catch (RuntimeException $e) {
echo $e->getMessage(), PHP_EOL;
}
$records = [
"!Type:Bank\nD31/02/2026\n^", // a date that does not exist
"!Type:Bank\nT1,234\n^", // 1234 or 1.234?
"!Type:Bank\nT12 EUR\n^", // not a number
"!Type:Bank\nT-20\nSTax\n^", // a split without its $ line
];
foreach ($records as $content) {
try {
(new Parser($content))->parse();
} catch (ParseException $e) {
echo $e->getMessage(), PHP_EOL;
}
}
// Where it happened, and the transactions read before
$parser = new Parser("!Type:Bank\nD25/12/2026\nT-20\n^\n\nD26/12/2026\nT12 EUR\n^\n");
try {
$parser->parse();
} catch (ParseException $e) {
printf("Line %d, record index %d: %s\n", $e->getLineNumber(), $e->getRecordIndex(), $e->getReason());
printf("%d transaction read before it\n", count($parser->getTransactions()));
}
try {
(new Transaction(Types::Bank))->setAmount('1,234');
} catch (InvalidArgumentException $e) {
echo $e->getMessage(), PHP_EOL;
}
Output
File not found or not readable: missing.qif
Directory not writable: /missing/directory
Line 2 (record index 0): "31/02/2026" is not a valid date with the day first (DateFormat::DayFirst).
Line 2 (record index 0): "1,234" is ambiguous: "," can be a decimal or a thousands separator. Pass the decimal separator explicitly.
Line 2 (record index 0): "12 EUR" is not an amount.
Line 3 (record index 0): The split "Tax" has no amount ($ line).
Line 7, record index 1: "12 EUR" is not an amount.
1 transaction read before it
"1,234" is ambiguous: "," can be a decimal or a thousands separator. Pass the decimal separator explicitly.

ParseException extends RuntimeException: catching RuntimeException around parseFile() catches both the file errors and the parse errors. The full list, with every message, is in the exceptions reference.

A ParseException stops the parsing at the first value it cannot read. It tells where that value is:

Method Returns
getLineNumber() The line of the value, from 1, empty lines included. A split without amount is reported on its S line
getRecordIndex() The index the transaction would have had in getTransactions(), from 0: the number read before it
getReason() The message without the location: "12 EUR" is not an amount.

The transactions read before the error are not lost: after a failed parse(), getTransactions() returns them. Parser::parseFile() does not return the parser when it throws: to keep them, read the file yourself and parse it with the constructor:

use Silarhi\Qif\Exception\ParseException;
use Silarhi\Qif\Parser;
$parser = new Parser(file_get_contents('/path/to/export.qif'));
try {
$parser->parse();
} catch (ParseException $e) {
error_log($e->getMessage());
}
$transactions = $parser->getTransactions(); // every transaction before the error

The messages below are the reasons, without the Line 2 (record index 0): prefix.

Message Cause Fix
"01/31/2026" is not a valid date with the day first… Month-first dates (US versions of Quicken) Pass DateFormat::MonthFirst: see Dates
"31/02/2026" is not a valid date… A date that does not exist with either format Fix the file
"1,234" is ambiguous… A single , between 1 to 3 digits and 3 digits: 1234 or 1.234 Pass the DecimalSeparator of the file: see Amounts
"…" is not an amount with "." as decimal separator., "…" does not group its thousands by ",". An amount in the other convention than the DecimalSeparator passed Pass the other DecimalSeparator, or none to detect it
"12 EUR" is not an amount. Not a number Fix the file
The split "Tax" has no amount ($ line). An S line without its $ line, or with an empty one Fix the file

The parser never stores a guessed value, but it accepts records with missing fields and lines it does not know. Check what matters to you after parsing:

Input Result
A record without D or T line getDate() or getAmount() returns null
An empty field (a bare P, D or T line) Skipped: the getter returns null, as for a missing field
A record without any value (a stray ^, only empty fields) Skipped: it is not a transaction
An unknown !Type: (!Type:Memorized) getType() returns null
!Account, !Type:Cat, !Type:Class, !Type:Security, !Type:Prices lists Skipped: they hold no transactions
!Option: and !Clear: lines Ignored
An unknown cleared status Status::NotCleared
A last record without its closing ^ Kept
A $ or E line that does not follow an S line Ignored (the transfer amount of investment records)
A line with an unknown or ignored code Ignored: see How a file is read
Content that is not QIF at all Transactions with null fields, or a ParseException

For instance, to reject the transactions without a date:

foreach ($parser->getTransactions() as $transaction) {
if (null === $transaction->getDate()) {
throw new UnexpectedValueException('Transaction without a date: ' . $transaction->_raw);
}
}