承接 chemaclass/edifact-parser 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

chemaclass/edifact-parser

Composer 安装命令:

composer require chemaclass/edifact-parser

包简介

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

README 文档

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

Typed Accessor Methods (New! ✨)

Many segments now provide typed accessor methods for cleaner, self-documenting code:

// NAD (Name and Address) - No more magic array indices!
$nadSegment = $message->segmentByTagAndSubId('NAD', 'CN');
$name = $nadSegment->name();              // Instead of rawValues()[4]
$street = $nadSegment->street();          // Instead of rawValues()[5]
$city = $nadSegment->city();              // Instead of rawValues()[6]
$postalCode = $nadSegment->postalCode();  // Instead of rawValues()[8]
$country = $nadSegment->countryCode();    // Instead of rawValues()[9]

// QTY (Quantity) - With type conversion
$qtySegment = $lineItem->segmentByTagAndSubId('QTY', '21');
$quantity = $qtySegment->quantityAsFloat();  // Returns float
$unit = $qtySegment->measureUnit();          // e.g., 'PCE', 'KGM'

// PRI (Price) - With type conversion
$priSegment = $lineItem->segmentByTagAndSubId('PRI', 'AAA');
$price = $priSegment->priceAsFloat();  // Returns float

// DTM (Date/Time) - With date parsing
$dtmSegment = $message->segmentByTagAndSubId('DTM', '10');
$dateTime = $dtmSegment->asDateTime();  // Returns DateTimeImmutable or null

// Message type detection
$messageType = $message->messageType();  // Returns 'ORDERS', 'INVOIC', etc.

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) {
    // Use typed accessors for cleaner code
    $companyName = $nadSegment->name();
    // Or access raw values directly if needed
    $companyName = $nadSegment->rawValues()[4];
}

Fluent Query API (New! ✨)

Chain filters and transformations for powerful segment querying:

// Find all NAD segments with subId 'CN'
$consignees = $message->query()
    ->withTag('NAD')
    ->withSubId('CN')
    ->get();

// Find first supplier address
$supplier = $message->query()
    ->withTag('NAD')
    ->withSubId('SU')
    ->first();

// Get all NAD and LIN segments
$segments = $message->query()
    ->withTags(['NAD', 'LIN'])
    ->get();

// Filter by type
$addresses = $message->query()
    ->ofType(NADNameAddress::class)
    ->get();

// Custom filtering with predicates
$highValueItems = $message->query()
    ->withTag('PRI')
    ->where(fn($s) => $s->priceAsFloat() > 1000)
    ->get();

// Chain multiple filters
$germanSuppliers = $message->query()
    ->withTag('NAD')
    ->withSubId('SU')
    ->where(fn($s) => $s->countryCode() === 'DE')
    ->limit(10)
    ->get();

// Transform results
$companyNames = $message->query()
    ->withTag('NAD')
    ->map(fn($s) => $s->name());

// Check existence
if ($message->query()->withTag('UNS')->exists()) {
    // Process summary section...
}

// Count matching segments
$nadCount = $message->query()->withTag('NAD')->count();

Type-Safe Qualifiers with Constants (New! ✨)

Use predefined constants for common EDIFACT qualifiers to avoid magic strings and improve IDE autocomplete:

use EdifactParser\Segments\Qualifier\NADQualifier;
use EdifactParser\Segments\Qualifier\QTYQualifier;
use EdifactParser\Segments\Qualifier\PRIQualifier;

// NAD qualifiers - party roles
$buyer = NADQualifier::BUYER;           // 'BY'
$supplier = NADQualifier::SUPPLIER;     // 'SU'
$consignee = NADQualifier::CONSIGNEE;   // 'CN'
$carrier = NADQualifier::CARRIER;       // 'CA'

// QTY qualifiers - quantity types
$ordered = QTYQualifier::ORDERED;       // '21'
$dispatched = QTYQualifier::DISPATCHED; // '12'
$invoiced = QTYQualifier::INVOICED;     // '47'

// PRI qualifiers - price types
$netPrice = PRIQualifier::CALCULATION_NET;  // 'AAA'
$grossPrice = PRIQualifier::GROSS;          // 'AAF'
$listPrice = PRIQualifier::LIST;            // 'LIS'

// Use in queries
$buyers = $message->query()
    ->withTag('NAD')
    ->where(fn($s) => $s->partyQualifier() === NADQualifier::BUYER)
    ->get();

// Use in match expressions
$role = match ($segment->partyQualifier()) {
    NADQualifier::BUYER => 'Customer',
    NADQualifier::SUPPLIER => 'Vendor',
    default => 'Unknown'
};

Available qualifier constants:

  • NADQualifier - Party roles (BY, SU, CN, CZ, DP, IV, PR, CA, FW, MF, UC, WH)
  • QTYQualifier - Quantity types (1, 3, 11, 12, 21, 33, 46, 47, 48, 192)
  • PRIQualifier - Price types (AAA, AAB, AAE, AAF, AAG, CAL, CT, DIS, LIS, MIN, RRP)
  • DTMQualifier - Date/time types (137, 2, 10, 11, etc.)
  • RFFQualifier - Reference types (ON, CR, DQ, PD, etc.)

Building Segments with Fluent Builders (New! ✨)

Create segment objects programmatically with a fluent, type-safe API:

use EdifactParser\Segments\NADNameAddress;
use EdifactParser\Segments\QTYQuantity;
use EdifactParser\Segments\PRIPrice;
use EdifactParser\Segments\Qualifier\NADQualifier;
use EdifactParser\Segments\Qualifier\QTYQualifier;
use EdifactParser\Segments\Qualifier\PRIQualifier;

// Build NAD segment
$nadSegment = NADNameAddress::builder()
    ->withQualifier(NADQualifier::BUYER)
    ->withPartyId('123456')
    ->withName('ACME Corporation')
    ->withStreet('123 Main Street')
    ->withCity('Springfield')
    ->withPostalCode('12345')
    ->withCountryCode('US')
    ->build();

// Build QTY segment
$qtySegment = QTYQuantity::builder()
    ->withQualifier(QTYQualifier::ORDERED)
    ->withQuantity(100)
    ->withMeasureUnit('PCE')
    ->build();

// Build PRI segment
$priSegment = PRIPrice::builder()
    ->withQualifier(PRIQualifier::CALCULATION_NET)
    ->withPrice(99.99)
    ->withPriceType('CT')
    ->build();

// Use built segments
echo $nadSegment->name();           // 'ACME Corporation'
echo $qtySegment->quantityAsFloat(); // 100.0
echo $priSegment->priceAsFloat();    // 99.99

Message Statistics and Analysis (New! ✨)

Analyze EDIFACT messages to extract statistics and insights:

use EdifactParser\Analysis\MessageAnalyzer;

$analyzer = new MessageAnalyzer($message);

// Basic counts
$type = $analyzer->getType();              // 'ORDERS', 'INVOIC', etc.
$totalSegments = $analyzer->segmentCount(); // Total number of segments
$lineItems = $analyzer->lineItemCount();   // Number of line items
$addresses = $analyzer->addressCount();    // Number of NAD segments

// Segment-specific counts
$qtyCount = $analyzer->segmentCountByTag('QTY');
$priCount = $analyzer->segmentCountByTag('PRI');

// Extract unique values
$partyQualifiers = $analyzer->getPartyQualifiers();  // ['BY', 'SU', 'CN']
$currencies = $analyzer->getCurrencies();            // ['EUR', 'USD']

// Calculate totals
$totalAmount = $analyzer->calculateTotalAmount();      // Sum all MOA segments
$taxableAmount = $analyzer->calculateTotalAmount('125'); // Sum MOA with qualifier 125
$totalQty = $analyzer->calculateTotalQuantity();       // Sum all QTY segments
$orderedQty = $analyzer->calculateTotalQuantity('21'); // Sum ordered quantities

// Check for specific segments
if ($analyzer->hasSegment('UNS')) {
    // Message has summary section
}
if ($analyzer->hasSummarySection()) {
    // Shortcut for UNS check
}

// Get comprehensive summary
$summary = $analyzer->getSummary();
/*
[
    'message_type' => 'ORDERS',
    'total_segments' => 42,
    'line_items' => 5,
    'addresses' => 3,
    'party_qualifiers' => ['BY', 'SU', 'CN'],
    'currencies' => ['EUR'],
    'segment_counts' => [
        'NAD' => 3,
        'LIN' => 5,
        'QTY' => 5,
        'PRI' => 5,
        'MOA' => 2,
        'DTM' => 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());
    }
}

🐛 Debugging

Segment Inspection

// Convert segment to array
$array = $segment->toArray();
// Returns: ['tag' => 'NAD', 'subId' => 'CN', 'rawValues' => [...]]

// Convert segment to JSON
$json = $segment->toJson();
// Pretty-printed JSON output

// Detect message type
$type = $message->messageType();
echo "Processing {$type} message";  // e.g., "Processing ORDERS message"

Enhanced Error Messages

use EdifactParser\Exception\InvalidFile;

try {
    $result = $parser->parseFile('invalid.edi');
} catch (InvalidFile $e) {
    // Get detailed error information
    $errors = $e->getErrors();
    $context = $e->getContext();

    // Exception message includes formatted context
    echo $e->getMessage();
}

✅ Best Practices

Do

  • ✅ Use typed accessors (e.g., $nad->name()) instead of raw array indices
  • ✅ Always null-check segments — not all segments exist in every message
  • ✅ Use segmentByTagAndSubId() for single lookups, segmentsByTag() for multiple
  • ✅ Use type conversion methods (quantityAsFloat(), asDateTime()) when available
  • ✅ Use line items for order/invoice processing — cleaner than manual grouping
  • ✅ Add helper methods to custom segments for domain-specific logic

Avoid

  • ❌ Don't use magic array indices when typed accessors are available
  • ❌ 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.

chemaclass/edifact-parser 适用场景与选型建议

chemaclass/edifact-parser 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 3.97k 次下载、GitHub Stars 达 16, 最近一次更新时间为 2019 年 11 月 09 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「parser」 「edi」 「edifact」 「edifile」 「un-edifact」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 chemaclass/edifact-parser 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 chemaclass/edifact-parser 我们能提供哪些服务?
定制开发 / 二次开发

基于 chemaclass/edifact-parser 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 3.97k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 16
  • 点击次数: 20
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 16
  • Watchers: 3
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2019-11-09