liberu-genealogy/laravel-gedcom
Laravel GEDCOM provides a Laravel-friendly toolkit for working with GEDCOM genealogy files, including parsing, importing, exporting, and managing family tree records such as individuals, families, events, and sources through an Eloquent-based workflow.
Install and confirm the wiring
composer require liberu-genealogy/laravel-gedcom
The package ships with Laravel package auto-discovery, so the service provider and any facade should register without touching config/app.php. Run php artisan vendor:publish --provider="Liberu\Genealogy\Gedcom\GedcomServiceProvider" (check the README for the exact provider class in your installed version) to pull out any config or migration stubs before you write code against it.
Where to look first
README.md — the canonical surface area; the class names below come from there, not from guessing.src/GedcomGenerator.php — the export path. This is the class touched in v8.6.0, so it is the most actively maintained entry point.tests/ — the fixtures are usually real .ged snippets and are the fastest way to learn the expected shape of parsed records.Source class or interface — this is the abstraction resolved through the container as of v8.6.0.First use case: round-trip a file
The lowest-risk first task is a read-then-write round trip: parse a small GEDCOM file you already have, dump the resulting structure, then feed it back through the generator and diff the output. That single exercise tells you the record model, the ordering guarantees, and where the package normalises versus preserves raw lines — all before you build anything on top of it.
Resolve everything from the container rather than new-ing classes directly. Given the v8.6.0 change, the generator now pulls Source from the container, which means constructor injection and app() resolution give you the same object graph — but manual instantiation may not.
Constructor injection, not new
public function __construct(private GedcomGenerator $generator) {}
This is the pattern the package is steering you toward. Since Source is now container-resolved inside the generator, any binding you register for Source will be honoured during export. Direct instantiation bypasses that and will silently ignore your bindings.
Swap Source via a service provider
The practical payoff of the v8.6.0 change is testability and extendability. Bind your own implementation in a provider:
$this->app->bind(Source::class, DatabaseSource::class);
Day-to-day this lets you back exports with Eloquent models, an API client, or a fixture stub without forking the generator. Keep test bindings inside TestCase::setUp() and reset them in tearDown() — container bindings are global to the application instance and leak between tests otherwise.
Import as a queued job
GEDCOM files are line-oriented and can be tens of megabytes. Wrap parsing in a job, not a controller action:
Export as a streamed download
Generate the file, write it to a temp path or Storage, then response()->download(...)->deleteFileAfterSend(true). For large trees, generate into a stream and skip holding the full string in memory.
Facade where injection is awkward
In Blade, artisan commands, and quick scripts, the facade (if provided) or app(GedcomGenerator::class) keeps you on the container-resolved path. Inside domain services, prefer constructor injection.
Model the domain, not the file format
Map parsed individuals, families, and events onto your own models with explicit foreign keys. Treat the package as a translation layer at the boundary of your app: GEDCOM in → your models → GEDCOM out. This makes swapping or upgrading the package a contained change and lets you test your genealogy logic without touching real files.
Test with fixtures
Keep a handful of .ged files in tests/fixtures: minimal, one with non-ASCII names, one with a malformed line, one large enough to exercise batching. Assert on your models, not on the package's internal arrays.
Container resolution is now behavioural, not incidental
Before v8.6.0, Source was presumably constructed directly inside GedcomGenerator. Now it comes from the container. Consequences:
Source whose constructor needs arguments must be bound, or it will fail to resolve. Register a closure binding or use singleton with a factory rather than relying on autowiring.singleton binding means one Source instance is shared across an entire request and across multiple generator calls. If your source accumulates state during export, you will see duplicated or leaked records on the second export in the same process — which is exactly the kind of thing that only shows up in a queue worker. Prefer bind unless you have a reason.Source yourself and passing it in, re-check the constructor signature: it may have changed or gained a default.Laravel 13 support landed with a host pin
The CI change accepts Laravel 13 but pins the runner host, which usually signals that the matrix resolves differently on other hosts. If you run the package's own suite in your CI, mirror their approach — a composer update on a different image may produce a matrix you have not tested. For your application, verify on your actual PHP/Laravel combination rather than assuming the green badge covers it.
Encoding is the number one source of corruption
GEDCOM historically permits ANSEL as well as UTF-8, and older files routinely arrive in it. Non-ASCII names will silently mangle into mojibake if you assume UTF-8. Detect, convert at the boundary, and store UTF-8 internally. Round-trip a file with accented characters and a non-Latin name before trusting anything.
Line endings and continuations
The format is CRLF-oriented and long values are split across CONC (concatenate) and CONT (newline) lines. A parser that handles one but not the other will truncate notes and addresses in ways that are invisible until a user complains. Write a fixture with a deliberately long NOTE and assert the reassembled value.
Trailing whitespace in level numbers
GEDCOM level numbers are significant, and some exporters emit 0 @I1@ INDI with trailing spaces or tabs. If records vanish on import, dump the raw line with visible whitespace before assuming a logic bug.
Debugging: keep the raw text
When output looks wrong, the fastest path is to dd() the raw generated string or write it to storage/app/debug.ged and diff against a known-good file. Diffing two GEDCOM files line by line identifies ordering and tag problems far faster than stepping through the object model.
Ordering is not guaranteed
Do not rely on records coming back in file order, or on HEAD/TRLR neighbours being preserved. Sort explicitly in your own code if order matters downstream. Similarly, records without @X@ cross-references are not linkable — validate that individuals and families carry IDs before building relationships.
Extension points worth knowing
Source — the main supported customisation.Source (logging, caching, filtering) is a clean way to add cross-cutting behaviour without touching the generator.Upgrade hygiene
The package is at v8.x with an active changelog, so minor bumps have introduced behavioural changes (v8.6.0 is a live example). Pin to ^8.6 rather than ^8.0, read the changelog on every bump, and keep your round-trip fixture test in CI — it will catch a container-resolution shift or an encoding change in one run.
How can I help you explore Laravel packages today?