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
Requires
- php: >=8.0
- ext-json: *
- sabas/edifact: ^1.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.57
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
- rector/rector: ^2.0
- symfony/var-dumper: ^5.4
- vimeo/psalm: ^4.30
README
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
UNHsegment and ends with aUNTsegment. - 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
- example/printing-segments.php β Print all parsed segments line by line.
- example/extracting-data.php β Extract values from specific segments.
- example/context-segments.php β Traverse hierarchical context segments.
π 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.
- π Report issues
- π§ Submit a pull request
π See the contributing guide to get started.