commerceguys/addressing
A PHP 8+ addressing library powered by CLDR and Google's address data. It provides address formats for 200+ countries, subdivisions, translations, HTML/plain-text formatting, validation via symfony/validator, and zones.
What this is: commerceguys/addressing is a framework-agnostic PHP 8.0+ domain library — not a Laravel package. It provides a value-object address model (xAL-aligned field names, immutable with* mutators plus an AddressBuilder), CLDR-backed reference data (countries in 150+ locales, address formats for 200+ countries, hierarchical subdivisions for 62), two formatters (display and postal-label), Symfony Validator constraints, and a Zone matcher for territorial groupings. There is no service provider, facade, config file, migration, or Blade view. Every Laravel integration point is ours to build.
Architecture fit: Strong at the domain layer, absent at the framework layer. The library gives us three things we would otherwise hand-roll badly and maintain forever: (1) per-country address format metadata — which fields exist, their order, which are required, which are uppercased for mail sorting, and the correct field labels ("State" vs "Province" vs "Prefecture" vs "Region"); (2) postal-code and subdivision validation data; (3) country/subdivision reference lists with local-script names. That is a genuine differentiator for any product with cross-border checkout, shipping, or tax. The address model itself is deliberately persistence-ignorant, so it will not fight Eloquent — but it also will not help.
Integration feasibility: Composer-installable, MIT, no exotic extensions, no network calls, no database. The realistic integration surface is small and well-bounded:
CountryRepository, AddressFormatRepository, SubdivisionRepository as container singletons. They are stateless after construction and read packaged JSON; re-instantiating them per request is pure waste.AddressFormatConstraint / CountryConstraint behind Laravel validation rules so controllers and FormRequests keep a single error contract.AddressFormat (field presence/order/required/labels) via a Blade component.AddressInterface to Eloquent via a JSON column cast or flattened columns.The friction is concentrated in one place: validation. Adopting the library's checks means running symfony/validator alongside Laravel's validator, then translating violations into Laravel's error bag and localizing messages through Laravel's lang files. That is a deliberate two-validator-stack decision and should be made explicitly, not discovered in review.
Technical risk:
| Risk | Severity | Note |
|---|---|---|
| No parser — the library formats and validates, it does not parse free-text addresses | High | Any backfill of legacy unstructured addresses needs a separate vendor or manual re-entry. Scope this before committing to a migration. |
| Data-driven release cadence | Medium | Releases routinely contain data corrections (postal-code patterns, renamed subdivisions, e.g. TR Afyon → Afyonkarahisar). Version pins need a review habit, not blind composer update. |
| API deprecations at minor versions | Low–Medium | v2.3.0 replaced subdivision_depth with subdivision_data_fields and deprecated getSubdivisionDepth(). Legacy definitions still convert, but custom configs must be tracked. |
| Maintainer concentration / bus factor | Medium | Small core team plus community PRs; no commercial SLA. Mitigated by MIT + packaged static data (forkable, data is in-repo). |
| Metadata hygiene | Low | The "Dependents: 0" figure contradicts the library's actual footprint (it is the engine behind Drupal's Address module and Drupal Commerce). Verify via Packagist before using it as an adoption signal; the future-dated "last release" also warrants a sanity check. |
Key questions for engineering:
symfony/validator in-process, or do we reimplement the constraints as native Laravel rules and accept divergence from upstream data?Zone (shipping rates, VAT territory logic), or is that already owned by a tax/fulfilment vendor? Duplicating zone logic in two systems is a defect generator.Stack fit: Clean. Pure PHP, PSR-friendly, no global state, no Laravel version coupling. It composes with Laravel 9–12 and PHP 8.0–8.5 without conflict; the only transitive dependency of consequence is symfony/validator, which most Laravel apps already have somewhere in the tree.
Recommended shape:
composer require commerceguys/addressing with a caret pin, plus a Renovate/Dependabot rule that surfaces data-only releases for changelog review rather than auto-merging.AddressServiceProvider registering the three repositories as singletons and publishing a config file for default locale and formatter options.PostalAddress castable / value-object mapper between Eloquent and AddressInterface.AddressFormat-driven Blade component for capture, and a DefaultFormatter-backed component for display (its HTML output is already semantic and translate="no"-annotated, which is a small accessibility/localization win).ValidAddressFormat, ValidCountry) delegating to the Symfony constraints, with violation messages mapped into the app's translation namespace.Migration path: Phased, and gated on the parser question.
PostalLabelFormatter is a genuine differentiator if we print fulfilment labels or carrier manifests — it handles the UPU convention of adding the English country name for international mail and suppressing the country name domestically. Scoped narrowly it is a half-day of work.Compatibility notes: Subdivision coverage is 62 countries, not 200. For the remainder, administrative areas must be free-text, so the UI cannot hard-assume a dropdown everywhere — the subdivision_data_fields metadata is the switch that must drive this. Also: store canonical codes, format for display. Uppercasing is a presentation concern applied by PostalLabelFormatter; never persist uppercased values.
Maintenance: Low code-maintenance, non-zero data-maintenance. The library version is the data version; address formats, postal-code patterns, and subdivision lists evolve with real-world administrative changes. Budget a recurring line item — roughly one engineer-day per quarter — for reviewing release notes, re-running the market QA checklist, and regression-testing postal-code validation on top markets. Because everything is packaged locally, there is no runtime dependency on a third-party service and therefore no availability or rate-limit exposure.
Support: Community-only. GitHub issues, no vendor SLA, no security bulletin channel. The security posture is favourable — no network I/O, no deserialization of untrusted input, no file writes — so CVE exposure is minimal, but supply-chain diligence still applies (pin versions, commit the lockfile, vendor-audit in CI).
Scaling: Effectively free. Operations are in-process array lookups and regex matches against packaged JSON; the cost is dominated by the first request in a process loading subdivision data. Two rules keep it flat: bind repositories as singletons, and do not construct them inside loops or per-line-item. Under Octane this is a benefit (data loads once per worker). No cache layer, no external calls, no cold-start problem worth modelling.
Failure modes to design for:
Ramp-up: Small. The README is genuinely executable — copy-paste examples for every feature — and the domain vocabulary (address format, subdivision, zone) is intuitive to anyone who has shipped checkout. A mid-level Laravel engineer should be productive in a day and confident in a week. The learning cost is not the library; it is the international address-formatting domain itself, and this library is the cheapest way to acquire that knowledge rather than reimplement it.
How can I help you explore Laravel packages today?