fsi/teryt-database-bundle
Symfony bundle for integrating Poland’s TERYT territorial register. Provides entities, import commands, and query helpers for voivodeships, counties, municipalities, localities, streets, and postal data, simplifying address and location validation in PHP applications.
Architecture fit. Despite being catalogued alongside PHP packages, this is a Symfony bundle, not a Laravel package. It ships a DI extension, Doctrine ORM entities, and console commands for importing GUS TERYT datasets (TERC — administrative units, SIMC — localities, ULIC — streets, WMRODZ — identity/name variants). That distinction drives everything: in a Laravel codebase the package cannot be composer required and used as-is. What has value is the domain model and the import parser — the mapping of Polish territorial identifiers onto relational tables. Treat this as a reference implementation to fork or port, not a dependency to adopt.
Integration feasibility. The technical core is straightforward and low-magic: parse fixed-format GUS text/CSV files, upsert into dictionary tables, expose lookups. Expected data volume is modest — roughly 16 voivodeships, ~314 powiaty, ~2,500 gminy, and 100k+ localities/streets — so a full reload is minutes, not hours, and the read path is trivially cacheable. The hard part is not the code; it is the data contract with GUS. Their file layouts have changed over time, and there is no versioning convention that makes drift loud. A silent column shift produces a green import with corrupt records.
Technical risk. High relative to the apparent simplicity, and almost entirely unbundled risk:
Key questions for the maintainer / spike team:
Stack fit. Two viable paths, and the choice should be made before any code is written:
A third option is worth considering for larger systems: keep TERYT as an isolated reference service or schema behind a thin repository interface, so the dictionary's lifecycle (weekly GUS refresh) is decoupled from application deploys.
Migration path. Introduce TERYT as a lookup layer, never as a big-bang replacement. Sequence:
Compatibility. Confirm PHP constraint, Symfony major, Doctrine DBAL major, and target DB engine in a one-day spike before committing to a plan. Also verify the GUS file format version the parser targets against the currently published files — this is the most likely source of a nasty surprise.
Sequencing relative to other work. The import pipeline and schema are on the critical path for any downstream address feature. If a regulatory or e-invoicing deadline depends on TERYT identifiers, start the data-contract validation in week one — it has the longest tail and the least controllable inputs.
Maintenance. In practice we own this code. Expect to become the maintainer of the fork: GUS publishes register updates regularly, and the parser will need attention when formats shift. Assign explicit ownership — this class of component rots quietly if nobody is named.
Support. No vendor SLA. FSI Open may be responsive, but that's goodwill, not a contract. The domain documentation is in Polish; budget for someone who can read GUS's own published format specifications rather than relying on the package's README.
Scaling. Read traffic scales trivially — the dictionary is small, immutable between refreshes, and a natural fit for a cache or read replica. The scaling concern is the import job: batch the writes, drop and rebuild indexes around a full reload, and run it transactionally or into a shadow table with an atomic swap. Never import in-place against a live read path.
Failure modes to instrument:
Ramp-up. Low for a mid-level PHP developer — the codebase is small and conventional. The real learning curve is domain knowledge: the Polish administrative hierarchy and which identifier belongs in which business context. Pair the integration with whoever owns the regulatory requirements, and keep a short internal note on the TERYT ID model — that document will outlive the package.
Bottom line. Low-cost, low-glamour component whose risk is concentrated in data stewardship rather than engineering. Worth adopting if TERYT identifiers are a genuine business requirement; not worth adopting if a plain address field would do.
How can I help you explore Laravel packages today?