A standard library for declaring EDI parsers, because that's not a fun thing to do.
This is a very common use-case for many services that output data using a defined text-file layout, for example, many services (Ex: Payment providers) output some sort of layout file that follows a simplified column separated values that has a fixed size set.
For example, the following line (25 bytes):
120240503Hello World
Can be interpreted as
Range | Size | Column | Value |
---|---|---|---|
0 - 0 | 1 | Reg. type | 1 |
1 - 8 | 8 | Date | 2024-05-03 |
9 - 25 | 16 | Message | Hello World |
To provide a better developer experience while creating these parsers, this library provides some useful property attributes and a base class for declaring these types of data.
For example, to declare the previous line, we can declare it in code as the following:
final class ExampleLayoutType extends Registry {
#[Number(1)]
public int $type;
#[Date(8, format: '!Ymd')]
public DateTimeInterface $type;
#[Text(16)]
public string $message;
}
// We can parse the data directly
$example = ExampleLayoutType::from('120240503Hello World ')
// Or we can hydrate it
$example = new ExampleLayoutType();
$example->hydrate('120240503Hello World ');
Using our built in line parser, we can create dynamically parsed (by line) types, with the power of generators, these types are parsed on demand:
// Using a standard line parser, iterates over each line
class EDIParser extends LineParser
{
// Ran on each line iteration
protected function parse(LineContext $ctx): ?Registry
{
[$contents, $number] = $ctx->unwrap();
// For this EDI file, we can deduce which type it is based on
// the first 2 letters provided each line.
$code = substr($contents, 0, 2);
try {
return match ($code) {
EDIRegistry::TYPE_HEADER_START => EDIHeader::from($contents),
EDIRegistry::TYPE_TRANSACTION_BATCH_START => EDITransactionBatch::from($contents),
EDIRegistry::TYPE_SALE_RECEIPT => EDISaleReceipt::from($contents),
// Our LineContext can provide a more verbose message to inform
// where in our data the error ocurred.
default => $ctx->raise("Cannot parse EDI of type '$code'", 0),
};
} catch (FieldException $e) {
$ctx->raise($e->getMessage(), $e->getCursor());
}
}
}
// Using our parser directly
// Reading directly from stdin
$buffer = Stream::file('php://stdin', 'rb');
$parser = EDIParser::loadFromStream($buffer);
foreach ($parser as $registry) {
/** @var Registry $registry */
// We have direct access to our parsed objects on demand
dump($registry);
}
This feature is work-in-progress, there are a some things we can do to make it better
We enable out-of-the-box support for friendly contextual messages on any errors that ocurred.
PHP Fatal error: Uncaught Kubinyete\Edi\Parser\Exception\ParseException: Line 1: Failed to parse field '202d1001025840' as a date with format 'YmdHis'
Contents: "A00003.1202d1001025840000442ADIQ SOLUCOES PAGAMENTOS S.A 0040002783189N000001"
--------^
in /home/vitorkubinyete/code/edi-php/src/Parser/LineContext.php:38