chemaclass/edifact-parser

An EDIFACT file parser to extract the values from any defined segment

Fund package maintenance!
chemaclass.com/sponsor

Installs: 3 710

Dependents: 0

Suggesters: 0

Security: 0

Stars: 16

Watchers: 3

Forks: 3

Open Issues: 0

pkg:composer/chemaclass/edifact-parser

5.2.0 2025-05-30 00:25 UTC

This package is auto-updated.

Last update: 2025-11-05 21:53:59 UTC


README

Scrutinizer Code Quality Type Coverage CI PHP Version

EDIFACT stands for Electronic Data Interchange For Administration, Commerce, and Transport.

This package provides a robust and extensible PHP parser to read, interpret, and extract data from EDIFACT-formatted files.

πŸ” Not sure what EDIFACT is? Learn more here

πŸ“š EDIFACT Format Overview

  • A file is composed of multiple segmentsβ€”each begins with a tag (e.g., UNH, NAD).
  • Each segment contains structured data relevant to that tag.
  • A message typically starts with a UNH segment and ends with a UNT segment.
  • A transaction is a list of such messages within a file.

πŸ‘‰ Read more about segments here

πŸ’Ύ Installation

Install via Composer:

composer require chemaclass/edifact-parser

πŸ§ͺ Examples

πŸ”Ž Usage example

<?php declare(strict_types=1);

use EdifactParser\EdifactParser;

require dirname(__DIR__) . '/vendor/autoload.php';

$fileContent = <<<EDI
...
NAD+CN+++Person Name+Street Nr 2+City2++12345+DE'
...
EDI;

$parser = EdifactParser::createWithDefaultSegments();
$parserResult = $parser->parse($fileContent);
// Or directly from a file
//$parserResult = $parser->parseFile('/path/to/file.edi');
$firstMessage = $parserResult->transactionMessages()[0];

$nadSegment = $firstMessage->segmentByTagAndSubId('NAD', 'CN');
$personName = $nadSegment->rawValues()[4]; // 'Person Name'

πŸ“‚ More Examples

πŸ“– Usage Guide

Accessing Segments

// Direct lookup by tag and subId (fastest)
$nadSegment = $message->segmentByTagAndSubId('NAD', 'BY');

// Get all segments with the same tag
$allNadSegments = $message->segmentsByTag('NAD');

// Always null-check when accessing segments
if ($nadSegment) {
    $companyName = $nadSegment->rawValues()[4];
}

Working with Line Items

Line items group LIN segments with their related data (QTY, PRI, PIA, etc.) β€” useful for processing orders and invoices:

foreach ($message->lineItems() as $lineItem) {
    $linSegment = $lineItem->segmentByTagAndSubId('LIN', '1');
    $qtySegment = $lineItem->segmentByTagAndSubId('QTY', '21');

    $productId = $linSegment->rawValues()[3];
    $quantity = $qtySegment->rawValues()[1][0];
}

Navigating Hierarchical Segments

Context segments maintain parent-child relationships (e.g., NAD β†’ CTA β†’ COM):

foreach ($message->contextSegments() as $context) {
    if ($context->tag() === 'NAD') {
        $address = $context->segment()->rawValues();

        foreach ($context->children() as $child) {
            if ($child->tag() === 'CTA') {
                $contactName = $child->rawValues()[2];
            }
        }
    }
}

Global vs Transaction Segments

// Global segments (file-level): UNA, UNB, UNZ
$globalSegments = $result->globalSegments();

// Transaction messages (UNH...UNT blocks)
foreach ($result->transactionMessages() as $message) {
    // Process each message...
}

πŸ”§ Extending with Custom Segments

Step 1: Create Your Segment Class

<?php
namespace YourApp\Segments;

use EdifactParser\Segments\AbstractSegment;

/** @psalm-immutable */
final class LOCLocation extends AbstractSegment
{
    public function tag(): string
    {
        return 'LOC';
    }

    // Optional: Add helper methods
    public function locationType(): string
    {
        return $this->rawValues()[1] ?? '';
    }

    public function locationCode(): string
    {
        return $this->rawValues()[2][0] ?? '';
    }
}

Step 2: Register with SegmentFactory

use EdifactParser\Segments\SegmentFactory;
use YourApp\Segments\LOCLocation;

$factory = SegmentFactory::withSegments([
    ...SegmentFactory::DEFAULT_SEGMENTS,  // Keep defaults
    'LOC' => LOCLocation::class,          // Add yours
]);

$parser = new EdifactParser($factory);

Step 3: Write Tests

use PHPUnit\Framework\TestCase;
use YourApp\Segments\LOCLocation;

final class LOCLocationTest extends TestCase
{
    /** @test */
    public function it_parses_location_data(): void
    {
        $raw = ['LOC', '11', ['DEHAM', '139', '6'], 'Hamburg'];
        $segment = new LOCLocation($raw);

        self::assertEquals('LOC', $segment->tag());
        self::assertEquals('11', $segment->subId());
        self::assertEquals('DEHAM', $segment->locationCode());
    }
}

βœ… Best Practices

Do

  • βœ… Always null-check segments β€” not all segments exist in every message
  • βœ… Use segmentByTagAndSubId() for single lookups, segmentsByTag() for multiple
  • βœ… Check field types before accessing β€” some rawValues() fields are arrays
  • βœ… Use line items for order/invoice processing β€” cleaner than manual grouping
  • βœ… Add helper methods to custom segments for domain-specific logic

Avoid

  • ❌ Don't assume segments exist β€” wrap in conditionals
  • ❌ Don't hardcode subIds β€” they vary by message type
  • ❌ Don't modify library segment classes β€” extend with custom segments instead
  • ❌ Don't parse raw values without checking types

πŸ› οΈ Development

Commands

composer install                        # Install dependencies

# Testing
composer test                           # Run all tests

# Code Quality
composer quality                        # Run all checks
composer csfix                          # Fix code style
composer psalm                          # Static analysis (Psalm)
composer phpstan                        # Static analysis (PHPStan)
composer rector                         # Apply refactoring rules

Code Standards

  • PHP 8.0+, strict types, PSR-4 autoloading
  • All code must pass PHP-CS-Fixer, Psalm, PHPStan, and Rector
  • Type hints required for all methods
  • Tests required for new functionality

🀝 Contributing

We welcome contributions of all kindsβ€”bug fixes, ideas, and improvements.

πŸ“‹ See the contributing guide to get started.