Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Gedcom Laravel Package

liberu-genealogy/php-gedcom

A PHP library for reading, writing, and manipulating GEDCOM genealogy files. It offers an object-oriented API for parsing individuals, families, events, and records, simplifying import and export of family-tree data in Laravel or any PHP application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and pin the minor version while the GEDCOM-X work is still marked WIP:

composer require liberu-genealogy/php-gedcom:^4.2

First places to look:

  • src/Gedcom/Parser.php — the entry point; parse() accepts a file path or raw string and returns a Gedcom object.
  • src/Gedcom/Record/ — the typed record classes (Indi, Fam, Sour, Repo, Note, Obje, etc.). Each record exposes getXref(), getUid(), and typed accessors for its sub-tags.
  • tests/ — the fixtures are the fastest way to see how real-world files (with CONC/CONT continuation, _UID, and vendor-specific tags) are represented in memory.

Minimal first use case — a queued import job for an uploaded .ged file:

$parser = new \Gedcom\Parser();
$gedcom = $parser->parse(Storage::path($upload->path));

foreach ($gedcom->getIndi() as $indi) {
    // $indi->getXref()   e.g. "I0042"
    // $indi->getName()   array of \Gedcom\Record\Indi\Name
    // $indi->getUid()    array of UID/_UID values (v4.2.0+)
}

Don't render from the parse in the request cycle — dispatch a job and persist to your own Eloquent models so you can query, diff, and re-import later.

Implementation Patterns

Import pipeline. Validate the upload (extension + a quick 0 HEAD sniff on the first line), move it to a private disk, record the original filename and checksum, then dispatch a job. Keep the raw file around: it is your audit trail and your only reliable way to reproduce a bad import.

class ImportGedcom implements ShouldQueue
{
    public function handle(GedcomImporter $importer): void
    {
        $importer->import($this->upload, $this->tree);
    }
}

Map to Eloquent with a stable key. The single most important decision is what you key on. GEDCOM xref (@I0042@) is file-local and changes on every re-export from desktop software, so never use it as a primary key. Use UID/_UID as the durable external identity, with the xref stored as a secondary gedcom_xref column scoped to the import. v4.2.0 adding UID support to all record types means you can now apply the same upsert strategy to sources, repositories, notes, and media — not just individuals and families.

Individual::upsert(
    $rows,
    uniqueBy: ['tree_id', 'uid'],
    update: ['gedcom_xref', 'surname', 'given_names', 'updated_at']
);

Prefer upsert over updateOrCreate in the loop; a 50k-individual file will otherwise spend most of its time on round trips.

Dedupe on re-import. With UIDs available everywhere, a second import of the same file should be idempotent. Compute a checksum of the file first and short-circuit if unchanged; otherwise upsert on (tree_id, uid) and soft-delete records present in the DB but absent from the file (guarding behind a confirmation for the first import of a new file).

GEDCOM-X export. The new mapping layer converts GEDCOM records into GEDCOM-X (JSON) for FamilySearch-style consumers. Treat it as an adapter boundary, not a core model:

$gedcomX = (new \Gedcom\GedcomX\Mapper())->map($gedcom);
return response()->json($gedcomX->toArray());

Because this is explicitly WIP, wrap it in your own GedcomXExporter class with a feature flag and a contract test against a checked-in expected JSON. When the shape shifts in a later release, you fix one class instead of every call site.

UIDs propagate to export. If you build GEDCOM-X by hand rather than via the mapper, remember v4.2.0 now surfaces UID/_UID on export — preserving them is what lets a downstream consumer round-trip the record back to you without creating a duplicate person.

Multi-tree, multi-tenant. One trees row per uploaded file lineage; every individual/family/source row carries tree_id. This makes "delete this import" a single scoped query and prevents cross-family merges.

Testing. Store small (5–20 line) GEDCOM fixtures under tests/Fixtures/gedcom/, one per quirk you support: CONC/CONT splitting, ANSEL-encoded names, _UID without UID, missing xrefs. Assert on your Eloquent state, not on the package's internal objects, so package upgrades don't churn your test suite.

Gotchas and Tips

  • UID vs _UID. Both appear in the wild. UID is the spec tag; _UID is a common vendor extension (Frequently from older FamilySearch/PAF exports). Your resolver should try UID, fall back to _UID, and only then fall back to a hash of xref + name. Log when you take the fallback — it usually means the file came from software that will not round-trip cleanly.
  • GEDCOM-X mapping is WIP. PR #95 is still in progress. Don't build a public API contract on the mapped output without a feature flag and a pinned minor version (^4.2 in composer.json, not *). Expect the JSON shape — not necessarily the method signatures — to move.
  • Line wrapping. GEDCOM caps logical lines at 255 characters and splits with CONC (concatenation, no whitespace) and CONT (newline). If you ever touch raw text before parsing, you will silently corrupt long note fields. Do all text manipulation on the parsed objects.
  • Encodings. HEAD/CHAR declares UTF-8, ANSEL, or ASCII. ANSEL is not a PHP-supported encoding; if you accept files from older desktop software you need a transliteration step or you'll get mojibake in names — which then breaks UID-based matching if you ever key on name.
  • Dates are not Carbon. GEDCOM dates are fuzzy by design: ABT 1850, BET 1840 AND 1845, (1850). Store the raw string alongside your best-effort parsed date_from/date_to range. Casting to a single Carbon column loses information permanently and cannot be recovered without re-parsing.
  • Memory on large files. A 100MB GEDCOM can balloon several hundred MB in PHP objects. Check memory_limit in the job, set $timeout generously, and consider chunking by top-level record if you hit limits. ShouldQueue without a timeout override will kill long imports at 60s.
  • Unknown tags are your friend. Vendor tags (_APID, _MILT, _WEBTAG) are preserved on the record. Before discarding them, consider whether a client will expect them back on export — round-trip fidelity is often the actual requirement.
  • Debugging a mis-parse. Bisect the input file down to the offending top-level record and re-parse in php artisan tinker with dd($record->toArray()). Most "broken" imports are a single malformed CONT block or an unterminated NOTE.
  • Extension point. Parser accepts a custom record-class map; use it if you need typed access to a proprietary tag rather than reaching into the generic sub-record bag every time.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle