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

Addressing Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the library alongside the optional validator integration:

composer require commerceguys/addressing symfony/validator

Nothing is auto-discovered. The fastest productive move is to bind the three repositories as singletons in a service provider — they parse large JSON/PHP data files and memoize results internally, so you do not want a new instance per request call:

// app/Providers/AddressingServiceProvider.php
use CommerceGuys\Addressing\AddressFormat\AddressFormatRepository;
use CommerceGuys\Addressing\Country\CountryRepository;
use CommerceGuys\Addressing\Subdivision\SubdivisionRepository;

public function register(): void
{
    $this->app->singleton(CountryRepository::class);
    $this->app->singleton(AddressFormatRepository::class);
    $this->app->singleton(SubdivisionRepository::class);
}

First use case to build: a country <select> plus an address form whose fields are driven by AddressFormat. Look first at:

  • src/Address.php — the default immutable value object you will map to Eloquent.
  • src/AddressFormat/AddressFormat.php — getUsedFields(), getRequiredFields(), getFieldOrder(), getUppercaseFields(), getPostalCodePattern(), getSubdivisionDataFields(), plus the localized labels (getAdministrativeAreaType(), getLocalityType()).
  • src/Formatter/DefaultFormatter.php and PostalLabelFormatter.php — for displaying/printing.
  • src/Validator/Constraints/AddressFormatConstraint.php — the actual validation logic.

A 20-line smoke test in tinker:

$formats = app(AddressFormatRepository::class);
$format = $formats->get('JP');           // field order, required fields, labels
$countries = app(CountryRepository::class)->getList('ja'); // code => localized name

Implementation Patterns

Persist the address as a single castable value object. The library is deliberately storage-agnostic (Address is immutable, PSR-7-style with* mutators). Mirror the Doctrine-embeddable advice with an Eloquent custom cast:

class AddressCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): ?Address
    {
        $data = json_decode($value ?? '', true);
        if (! $data) return null;
        $a = new Address();
        foreach (['countryCode', 'administrativeArea', 'locality', 'dependentLocality',
                  'postalCode', 'sortingCode', 'addressLine1', 'addressLine2', 'addressLine3',
                  'organization', 'givenName', 'additionalName', 'familyName'] as $f) {
            if (! empty($data[$f])) $a = $a->{'with' . ucfirst($f)}($data[$f]);
        }
        return $a;
    }

    public function set($model, $key, $value, $attributes): array
    {
        return [$key => json_encode($value ? [
            'countryCode' => $value->getCountryCode(),
            'addressLine1' => $value->getAddressLine1(),
            // ...etc
        ] : null)];
    }
}

Store the JSON column (addresses.address or separate country_code/postal_code columns if you need to index/sort them for zone lookups). Keep subdivision codes (CA, CE), not names — names change with locale and CLDR updates.

Drive the form from the format, not from hardcoded assumptions. In a Blade partial or Livewire component:

$format = app(AddressFormatRepository::class)->get($countryCode);
$used = $format->getUsedFields();       // ['addressLine1','locality','administrativeArea','postalCode',...]
$required = $format->getRequiredFields();
$order = $format->getFieldOrder();      // e.g. ['addressLine1','postalCode','locality','administrativeArea']

Label the locality field with $format->getLocalityType() (“Town”, “Municipality”, “City”) and the administrative area with $format->getAdministrativeAreaType() (“State”, “Province”, “Prefecture”). Hide fields not in $used. This single mechanism is why the package is worth using — Japanese, Brazilian, and Irish addresses all render correctly from the same template.

Cascading subdivision selects. Expose one AJAX endpoint:

Route::get('/subdivisions/{country}/{parent?}', function (SubdivisionRepository $repo, string $country, ?string $parent = null) {
    $parents = $parent ? [$country, $parent] : [$country];
    return collect($repo->getAll($parents))
        ->map(fn ($s) => ['code' => $s->getCode(), 'name' => $s->getName(), 'has_children' => $s->hasChildren()]);
});

Check has_children to decide whether to render a third select (Administrative Area → Locality → Dependent Locality, max three levels).

Validation: two constraints, in order. Wrap it in a custom Laravel rule:

class ValidAddressFormat implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $validator = Validation::createValidator();
        if ($validator->validate($value->getCountryCode(), new CountryConstraint())->count()) {
            $fail('Unknown country.'); return;
        }
        foreach ($validator->validate($value, new AddressFormatConstraint()) as $violation) {
            $fail($violation->getMessage());
        }
    }
}

AddressFormatConstraint also verifies that fields not used by the country format are empty — so a stray administrativeArea=CA submitted with countryCode=DE fails rather than silently persisting. That is a feature; lean on it.

Formatting output. Use DefaultFormatter for UI (it always appends the localized country name and emits semantic <span> classes) and PostalLabelFormatter for invoices, packing slips, and shipping labels — it uppercases exactly the fields the destination format requires for automated sorting and adds the bilingual country name for international mail:

$label = (new PostalLabelFormatter($formats, $countries, $subdivisions, ['locale' => app()->getLocale()]))
    ->format($address, ['origin_country' => config('shop.origin_country')]);

Zones for shipping and tax. Zone::match($address) handles country + subdivision + postal code inclusion/exclusion, including ranges (6691, 6991:6993) and regexes. Persist zones (e.g. as JSON in shipping_zones) and match on cart/checkout to select shipping rates or VAT rules. This replaces a lot of bespoke “does this postcode belong to…” code.

Gotchas and Tips

  • Address is immutable. $address->withLocality('X') returns a new object; forgetting to reassign is the most common first bug. Consider a small mutable wrapper or an AddressBuilder from your form data.
  • Repositories are expensive to construct. Never new CountryRepository() inside a loop or a Blade loop; resolve from the container.
  • Locale matters. CountryRepository::getList($locale) only knows the locales shipped with CLDR (~150); en is the safe fallback. Set the locale per request rather than at boot, and be aware country names in dropdowns change when the app language changes — never store the name, only the code.
  • Clear hidden fields on country change. Because unused fields must be empty, switching from US to DE in a JS-enhanced form leaves a stale administrativeArea that fails validation. Reset the whole address subform (or only allowed fields) when country_code changes.
  • Validate the country code first. AddressFormatConstraint requires a valid countryCode; without the CountryConstraint pre-check you get confusing downstream messages for empty/garbage countries.
  • Postal code patterns are not user-friendly. getPostalCodePattern() is a raw regex (\d{5}(-\d{4})? style). Expose a human hint like “12345” derived from the pattern (\d → 0), and do not reimplement the regex yourself — subdivision-level overrides exist (some countries differ by state).
  • Subdivision values are codes, not names. Validation matches against predefined subdivision codes. If users type free text into a state field, validation fails. Always render a select for countries where subdivision_data_fields includes administrativeArea.
  • API change in v2.3: getSubdivisionDepth() is deprecated in favor of getSubdivisionDataFields() (e.g. ['administrativeArea']). Custom AddressFormat definitions using subdivision_depth are still converted automatically, but new code should read the new method.
  • PostalLabelFormatter requires origin_country (constructor or format() options) and throws otherwise. Put it in config/shop.php rather than passing it ad hoc.
  • HTML output is already escaped. Do not {!! !!} blindly if you override the wrapper; the formatter escapes field values but lets you pass wrapper element/attributes — validate any attribute values that come from user input.
  • PHP/Symfony version coupling. v2.x requires PHP 8.0+ and pins a symfony/validator range; if your app already constrains Symfony packages, run composer why symfony/validator before upgrading. The validator integration is a separate dependency — the core library works without it.
  • Testing. Seed factories with real per-country fixtures (US/CA/Mountain View/94043, JP/沖縄県/…, BR/CE/…) and assert both formatting and AddressFormatConstraint results. This catches CLDR data updates that change labels or add subdivisions without you noticing.
  • Extension point: if you need custom address formats (e.g. an internal “warehouse” pseudo-country), subclass AddressFormatRepository and override get(), then rebind it — the formatter, validator, and your form builder all consume the same interface.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator