Parsing
use Silarhi\Caf\Parser\PaymentSlipParser;
$parser = new PaymentSlipParser();$paymentSlip = $parser->parse($content);parse() takes the content of the file as a string: read it from disk, an upload or a remote storage, the parser never
opens files. It keeps no state between calls, so one instance can parse any number of files.
The table
Section titled “The table”The parser looks for lines of 121 dashes. The rows are the lines between the 2nd and the 3rd of them, and a 4th must
follow (the total row sits between the 3rd and the 4th). Without these four borders, parse() throws
Input CAF LA44 could not be parsed.
Each line between the 2nd and the 3rd borders is a row, split on : into exactly ten parts: the eight columns, plus
what comes before the first : and after the last one, which are ignored. Every value is trimmed. A row with another
number of parts, a blank line between two rows or an empty table throws CAF Row n°<row> could not be parsed, counting
rows from 1 at the first row of the table. Blank lines before the first row or after the last one are ignored.
Large files are fine: the table is located without regular expressions, and the test suite parses 10,000 rows.
| Value | Format | Result |
|---|---|---|
Row period (DATE DEBUT, DATE FIN) |
m Y (PaymentSlipParser::DATE_FORMAT), e.g. 01 2026 |
The 1st of the month at midnight |
| Processing and payment dates | d m Y (PaymentSlipParser::FULL_DATE_FORMAT), e.g. 05 02 2026 |
That day at midnight |
Dates are DateTimeImmutable instances in the default timezone of PHP; the getters declare DateTimeInterface. Each
line gets its own instances, even when rows share the same month. Several spaces between the parts of a processing or
payment date are accepted (29 02 2024).
Invalid or out-of-range dates throw a ParseException instead of rolling over: 13 2021, 00 2022 or AB 2022 in a
row, 31 02 2021 or 29 02 2021 in the header. See error handling.
Amounts
Section titled “Amounts”Amounts are returned as float, in the rows and in the total. Spaces, no-break spaces (U+00A0) and narrow no-break
spaces (U+202F) are thousands separators. Then:
- a single comma or dot is the decimal separator:
1298,50and1298.50are both1298.5 - with both, the last one is the decimal separator and the other one groups the digits by 3: French (
1.298,50) and English (1,298.50) formats give the same result - a comma or dot repeated without decimals groups the digits by 3:
12.345.678or12,345,678
| In the file | Result |
|---|---|
272 |
272.0 |
1298,00 |
1298.0 |
1 298,50 |
1298.5 |
1.298,50 |
1298.5 |
1,298.50 |
1298.5 |
1298.50 |
1298.5 |
12 345 678,90 |
12345678.9 |
12.345.678 |
12345678.0 |
-12,50 |
-12.5 |
Any other amount throws a ParseException instead of returning a wrong value: an empty amount, letters, the same
separator for thousands and decimals (1.298.50), digits not grouped by 3 (12.34,50) or a trailing minus sign
(12,50-). See error handling. A blank total (TOTAL : :) is not an
error: getTotalAmount() returns null, like a missing one.
Metadata
Section titled “Metadata”Each metadata field is read on its own, from its label in the header (see LA44ZZ files). A
field the parser does not find stays null, and the rows are still parsed. With only the table, every field but the
total is null:
$paymentSlip->getProcessingDate(); // null$paymentSlip->getPaymentDate(); // null$paymentSlip->getCafName(); // null$paymentSlip->getCafAddress(); // null$paymentSlip->getRecipientName(); // null$paymentSlip->getRecipientAddress(); // null$paymentSlip->getReference(); // null$paymentSlip->getBic(); // null$paymentSlip->getIban(); // null$paymentSlip->getTotalAmount(); // 600.0count($paymentSlip->getLines()); // 3
$line = $paymentSlip->getLines()[0];$line->getReference(); // ""$line->getBeneficiaryReference(); // "1234567 A"$line->getBeneficiaryName(); // "MME EXEMPLE ALICE"$line->getStartDate(); // DateTimeImmutable '2026-01-01 00:00:00'$line->getEndDate(); // DateTimeImmutable '2026-01-01 00:00:00'$line->getGrossAmount(); // 312.0$line->getDeduction(); // 0.0$line->getNetAmount(); // 312.0
$line = $paymentSlip->getLines()[1];$line->getReference(); // "0000000000000"$line->getBeneficiaryReference(); // "2345678 B"$line->getBeneficiaryName(); // "MR EXEMPLE BRUNO"$line->getStartDate(); // DateTimeImmutable '2025-12-01 00:00:00'$line->getEndDate(); // DateTimeImmutable '2026-01-01 00:00:00'$line->getGrossAmount(); // 225.5$line->getDeduction(); // 25.5$line->getNetAmount(); // 200.0
$line = $paymentSlip->getLines()[2];$line->getReference(); // ""$line->getBeneficiaryReference(); // "3456789 C"$line->getBeneficiaryName(); // "MME EXEMPLE CHLOE"$line->getStartDate(); // DateTimeImmutable '2026-01-01 00:00:00'$line->getEndDate(); // DateTimeImmutable '2026-01-01 00:00:00'$line->getGrossAmount(); // 88.0$line->getDeduction(); // 0.0$line->getNetAmount(); // 88.0 ------------------------------------------------------------------------------------------------------------------------- : REFERENCES : NUMERO : NOM DESTINATAIRE : DATE : DATE : MONTANT : RETENUE : MONTANT : : LOGEMENT/PRET :ALLOCATAIRE: : DEBUT : FIN : BRUT : : NET : ------------------------------------------------------------------------------------------------------------------------- : : 1234567 A : MME EXEMPLE ALICE : 01 2026 : 01 2026 : 312,00: 0,00: 312,00 : : 0000000000000 : 2345678 B : MR EXEMPLE BRUNO : 12 2025 : 01 2026 : 225,50: 25,50: 200,00 : : : 3456789 C : MME EXEMPLE CHLOE : 01 2026 : 01 2026 : 88,00: 0,00: 88,00 : ------------------------------------------------------------------------------------------------------------------------- : TOTAL : 600,00 : -------------------------------------------------------------------------------------------------------------------------| Field | Read from | Accepts, e.g. |
|---|---|---|
getCafName() |
CAISSE D'ALLOCATIONS FAMILIALES, then the next line up to its right column: uppercase letters, spaces, ' and - |
DE L'AIN, DE SEINE-SAINT-DENIS |
getCafAddress() |
The text before NUMERO DE PRODUIT on its line (street), then the text before TYPE DE TRAITEMENT starting with a 5-digit postal code (city) |
8 AVENUE DES CANUTS, PLACE DE LA GARE, 42000 SAINT ETIENNE |
getRecipientName() |
The first line indented by 20 spaces or more that starts with a letter: letters, digits, spaces, -, ., ' and & |
SCI L'ETOILE, DUPONT & FILS, Mme Dupont |
getRecipientAddress() |
The end of the NM REFERENCE line (street), then the next line starting with a 5-digit postal code (city) |
LIEU-DIT LES PINS, 42000 SAINT-ETIENNE CEDEX 2 |
The CAF address is set only when both its street and its city are found. The patterns come from sample slips: a
header laid out differently gives null for that field.
Line endings
Section titled “Line endings”Windows (\r\n) and Unix (\n) line endings give the same result.
