Parse French CFONB 120 and 240 bank files into typed PHP objects.
Bank statements and transfers, with zero runtime dependencies.
- Features
- Requirements
- Installation
- Usage
- Advanced Usage Examples
- Troubleshooting
- Testing & Quality
- Contributing
- License
- Zero dependencies — only PHP is required, nothing else gets pulled into your project.
- CFONB 120 statements — daily
Statementobjects with opening/closing balances (01/07), operations (04) and their details (05). - CFONB 240 transfers —
Transferobjects with a header (31), transactions (34) and a total (39). - Signed amounts — decimals and sign characters are decoded: credits are positive, debits are negative.
- Typed value objects — getters return floats,
DateTimeInterfacedates and nullable strings, not raw arrays. - Tolerant input — handles
\nand\r\nline endings, and files with no line breaks at all. - Strict by default — malformed lines throw a
ParseExceptioninstead of producing silent garbage.
| Dependency | Version |
|---|---|
| PHP | 8.2+ |
composer require silarhi/cfonb-parser<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
// Gets all statements day by day
foreach ($reader->parse('My Content') as $statement) {
if ($statement->hasOldBalance()) {
echo sprintf("Old balance : %f\n", $statement->getOldBalance()->getAmount());
}
foreach ($statement->getOperations() as $operation) {
// Gets all statement operations
}
if ($statement->hasNewBalance()) {
echo sprintf("New balance : %f\n", $statement->getNewBalance()->getAmount());
}
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
$content = file_get_contents('/path/to/cfonb120-file.txt');
foreach ($reader->parse($content) as $statement) {
// Process each daily statement
echo "Statement date: " . $statement->getOldBalance()->getDate()->format('Y-m-d') . "\n";
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
foreach ($reader->parse($content) as $statement) {
foreach ($statement->getOperations() as $operation) {
echo "Date: " . $operation->getDate()->format('Y-m-d') . "\n";
echo "Value Date: " . $operation->getValueDate()->format('Y-m-d') . "\n";
echo "Amount: " . $operation->getAmount() . "\n";
echo "Label: " . $operation->getLabel() . "\n";
echo "Reference: " . $operation->getReference() . "\n";
echo "Bank Code: " . $operation->getBankCode() . "\n";
echo "Account: " . $operation->getAccountNumber() . "\n";
// Access optional fields
if ($operation->getInternalCode()) {
echo "Internal Code: " . $operation->getInternalCode() . "\n";
}
// Process operation details (additional information)
foreach ($operation->getDetails() as $detail) {
echo "Additional Info: " . $detail->getAdditionalInformations() . "\n";
}
echo "---\n";
}
}In strict mode, any line that cannot be parsed throws a ParseException. Non-strict mode only relaxes one rule: a blank
operation reference on a 04 line becomes an empty string instead of an error.
<?php
use Silarhi\Cfonb\Cfonb120Reader;
use Silarhi\Cfonb\Exceptions\ParseException;
$reader = new Cfonb120Reader();
// Strict mode (default) - throws an exception on parsing errors
try {
$statements = $reader->parse($content); // strict=true by default
} catch (ParseException $e) {
echo "Parsing error: " . $e->getMessage() . "\n";
}
// Non-strict mode - blank operation references are returned as ''
$statements = $reader->parse($content, false); // strict=false<?php
use Silarhi\Cfonb\Cfonb240Reader;
use Silarhi\Cfonb\Banking\Transfer;
$reader = new Cfonb240Reader();
foreach ($reader->parse('My Content') as $transfer) {
assert($transfer instanceof Transfer);
}<?php
use Silarhi\Cfonb\Cfonb240Reader;
$reader = new Cfonb240Reader();
foreach ($reader->parse($content) as $transfer) {
// Access transfer header information
$header = $transfer->getHeader();
echo "Transfer Date: " . $header->getPrevTransactionFileDate()->format('Y-m-d') . "\n";
echo "Bank Code: " . $header->getRecipientBankCode1() . "\n";
echo "Account: " . $header->getRecipientAccountNumber1() . "\n";
// Process all transactions in the transfer
foreach ($transfer->getTransactions() as $transaction) {
echo "Transaction Date: " . $transaction->getSettlementDate()->format('Y-m-d') . "\n";
echo "Amount: " . $transaction->getTransactionAmount() . "\n";
echo "Recipient: " . $transaction->getRecipientName1() . "\n";
echo "Reference: " . $transaction->getPresenterReference() . "\n";
}
// Access transfer total
$total = $transfer->getTotal();
echo "Total Amount: " . $total->getTotalAmount() . "\n";
echo "Sequence Number: " . $total->getSequenceNumber() . "\n";
}<?php
use Silarhi\Cfonb\Cfonb240Reader;
use Silarhi\Cfonb\Exceptions\ParseException;
use Silarhi\Cfonb\Exceptions\HeaderUnavailableException;
$reader = new Cfonb240Reader();
try {
$content = file_get_contents('/path/to/cfonb240-file.txt');
foreach ($reader->parse($content) as $transfer) {
// Safely access optional header
try {
$header = $transfer->getHeader();
echo "Processing transfer from: " . $header->getRecipientBankCode1() . "\n";
} catch (HeaderUnavailableException $e) {
echo "Transfer header not available\n";
}
// Process transactions...
}
} catch (ParseException $e) {
echo "Failed to parse CFONB file: " . $e->getMessage() . "\n";
}<?php
use Silarhi\Cfonb\CfonbReader;
use Silarhi\Cfonb\Banking\Statement;
use Silarhi\Cfonb\Banking\Transfer;
$reader = new CfonbReader();
foreach ($reader->parseCfonb120('My Content') as $statement) {
assert($statement instanceof Statement);
}
foreach ($reader->parseCfonb240('My Content') as $transfer) {
assert($transfer instanceof Transfer);
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
use Silarhi\Cfonb\Banking\Operation;
$reader = new Cfonb120Reader();
$statements = $reader->parse($content);
// Filter operations by amount
foreach ($statements as $statement) {
$largeOperations = array_filter(
$statement->getOperations(),
fn(Operation $op) => abs($op->getAmount()) > 1000
);
foreach ($largeOperations as $operation) {
echo "Large operation: {$operation->getAmount()} - {$operation->getLabel()}\n";
}
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
$statements = $reader->parse($content);
$summary = [
'total_debit' => 0,
'total_credit' => 0,
'operation_count' => 0
];
foreach ($statements as $statement) {
foreach ($statement->getOperations() as $operation) {
$amount = $operation->getAmount();
if ($amount > 0) {
$summary['total_credit'] += $amount;
} else {
$summary['total_debit'] += abs($amount);
}
$summary['operation_count']++;
}
}
echo "Summary:\n";
echo "Total Credits: {$summary['total_credit']}\n";
echo "Total Debits: {$summary['total_debit']}\n";
echo "Operation Count: {$summary['operation_count']}\n";<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
$statements = $reader->parse($content);
$data = [];
foreach ($statements as $statement) {
$statementData = [
'date' => $statement->getOldBalance()->getDate()->format('Y-m-d'),
'opening_balance' => $statement->hasOldBalance() ? $statement->getOldBalance()->getAmount() : null,
'closing_balance' => $statement->hasNewBalance() ? $statement->getNewBalance()->getAmount() : null,
'operations' => []
];
foreach ($statement->getOperations() as $operation) {
$statementData['operations'][] = [
'date' => $operation->getDate()->format('Y-m-d'),
'value_date' => $operation->getValueDate()->format('Y-m-d'),
'amount' => $operation->getAmount(),
'label' => $operation->getLabel(),
'reference' => $operation->getReference(),
'bank_code' => $operation->getBankCode(),
'account_number' => $operation->getAccountNumber()
];
}
$data[] = $statementData;
}
// Convert to JSON for API response
echo json_encode($data, JSON_PRETTY_PRINT);<?php
use Silarhi\Cfonb\Cfonb120Reader;
$reader = new Cfonb120Reader();
$statements = $reader->parse($content);
// Group statements by account
$accountStatements = [];
foreach ($statements as $statement) {
if ($statement->hasOldBalance()) {
$accountNumber = $statement->getOldBalance()->getAccountNumber();
$accountStatements[$accountNumber][] = $statement;
}
}
// Process each account separately
foreach ($accountStatements as $accountNumber => $statements) {
echo "Account: $accountNumber\n";
echo "Number of statements: " . count($statements) . "\n";
$totalBalance = 0;
foreach ($statements as $statement) {
if ($statement->hasNewBalance()) {
$totalBalance = $statement->getNewBalance()->getAmount();
}
}
echo "Final balance: $totalBalance\n\n";
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
use Silarhi\Cfonb\Exceptions\ParseException;
$reader = new Cfonb120Reader();
try {
$statements = $reader->parse($content);
} catch (ParseException $e) {
// Common issues:
// - Invalid line length (should be 120 characters)
// - Unknown record code (CFONB 120 supports 01, 04, 05 and 07)
// - Malformed line format
// - Invalid date format
// - Invalid amount format
echo "Parse error: " . $e->getMessage() . "\n";
// Non-strict mode only tolerates blank operation references,
// any other error is still thrown
$statements = $reader->parse($content, false);
}<?php
use Silarhi\Cfonb\Cfonb120Reader;
function validateCfonbFormat(string $content): bool {
// Normalize line endings, but keep trailing spaces: they are part of the 120 characters
$lines = explode("\n", trim(str_replace("\r\n", "\n", $content), "\n"));
foreach ($lines as $line) {
// Skip empty lines
if ('' === $line) continue;
// Check line length for CFONB 120
if (strlen($line) !== Cfonb120Reader::LINE_LENGTH) {
return false;
}
// Check if line starts with valid CFONB code
$lineCode = substr($line, 0, 2);
if (!in_array($lineCode, ['01', '04', '05', '07'], true)) {
return false;
}
}
return true;
}
// Usage
if (validateCfonbFormat($content)) {
$reader = new Cfonb120Reader();
$statements = $reader->parse($content);
} else {
echo "Invalid CFONB format\n";
}# Install dependencies
composer install
# Run tests
vendor/bin/phpunit
# Static analysis (PHPStan level: max)
vendor/bin/phpstan analyse
# Static analysis (Psalm)
vendor/bin/psalm
# Code style check
vendor/bin/php-cs-fixer fix --dry-run --diff
# Code style fix
vendor/bin/php-cs-fixer fix
# Code modernization check
vendor/bin/rector process --dry-runContributions are welcome! Please make sure your changes pass all quality checks before submitting a pull request:
vendor/bin/phpunit && vendor/bin/phpstan analyse && vendor/bin/psalm && vendor/bin/php-cs-fixer fix --dry-run --diffMIT License. See LICENSE for details.
Built with care by SILARHI.
If CFONB Parser saves you time, consider giving it a star on GitHub.