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

Address Format Laravel Package

torann/address-format

Torann/address-format is a PHP package that formats postal addresses using country-specific conventions. Provide address components and a country code to generate correctly ordered, multi-line output, with Laravel support and configurable templates for consistent address display.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify your PHP floor before Composer does it for you. 2.0.0 requires PHP 8.2+. If any service in your estate is still on 8.1, pin ^1.x deliberately rather than letting the resolver hand you a version the runtime can't load.

composer require torann/address-format
php artisan vendor:publish --provider="Torann\AddressFormat\AddressFormatServiceProvider"

Read two things before writing any code.

  1. config/address-format.php — the country → format-template map. This file is the package. Open the entries for your launch markets (US, GB, DE, JP) and check line order, separators, and which components appear at all. If a template is wrong for a market, that's a config override, not a code change.
  2. The README's single formatting example. It is effectively the entire API. You pass a component array plus a country/style and get a string back. There is no deep surface area to learn.

First use case: invoice PDFs and confirmation emails. Low risk, high visibility, and a formatting bug there surfaces as a support ticket rather than a failed shipment. Do not start at checkout or shipping-label generation.

Wire it behind an interface on day one:

// app/Contracts/AddressFormatter.php
interface AddressFormatter
{
    public function format(Address $address, string $style = 'multi_line'): string;
}
// app/Services/TorannAddressFormatter.php
class TorannAddressFormatter implements AddressFormatter
{
    public function format(Address $address, string $style = 'multi_line'): string
    {
        return AddressFormat::format([
            'streetAddress'      => $address->street_address,
            'locality'           => $address->city,
            'administrativeArea' => $address->state,
            'postalCode'         => $address->postal_code,
            'country'            => $address->country_code,
        ], $style);
    }
}

Bind it in a service provider and never reference the facade outside this adapter. That one habit is what makes the package's abandonment risk (5 stars, zero dependents, single maintainer) a non-event — a fork or a swap to commerceguys/addressing becomes a one-file change.

First-day checklist:

  • Confirm PHP 8.2+ on every consuming service, or pin ^1.x on purpose
  • Read the config templates for your launch markets
  • Spike: format 3–5 sample addresses per market, eyeball the output against a real envelope
  • Commit those outputs as golden files
  • Build the adapter + contract
  • Ship to invoice/email only

Implementation Patterns

Structured in, formatted out

Persist canonical structured columns (street_address, city, state, postal_code, country_code) as separate fields. Format only at render/print time. Never store the formatted string as the source of truth — a formatted blob cannot be re-localized when a template improves or a market is added. Cheap now, expensive to reverse.

Address value object instead of loose arrays

The package takes arrays, so passing Eloquent attributes straight through means a renamed column silently drops a line. Wrap it:

final class Address
{
    public function __construct(
        public readonly ?string $street_address,
        public readonly ?string $city,
        public readonly ?string $state,
        public readonly ?string $postal_code,
        public readonly string $country_code,
    ) {}

    public static function fromModel(ShippingAddress $m): self { /* ... */ }
}

Now a schema change is a single-point failure in one place, not silent misformatting in six. This is the highest-value pattern with this package, because the library performs no key validation — a mismatched key simply isn't there.

Eloquent accessor for the common case

// app/Models/Order.php
protected function shippingAddressFormatted(): Attribute
{
    return Attribute::make(
        get: fn () => app(AddressFormatter::class)
            ->format($this->shippingAddress(), 'multi_line'),
    );
}

Careful: this fires a container resolve plus a format on every attribute access, including inside loops. Use ->toArray() sparingly on collections of hundreds of orders, or eager-compute onto the model instance once.

Blade component, not facade calls in views

<x-address :address="$order->shippingAddress()" style="multi_line" />

Keep the Blade layer dumb. If a template needs to change for one market, the config changes — not the views. Calling AddressFormat:: from a template reintroduces the coupling the adapter existed to prevent.

Two render styles, one adapter

Most teams need exactly two shapes:

  • multi_line — confirmations, invoices, profile pages. Preserves the country's natural line breaks.
  • single_line — carrier payloads, CRM exports, CSV, log lines. Comma-joined, no newlines.

Build both at the adapter level so callers never think about separators. If the package only exposes one shape natively, derive the single-line variant from the same component array — don't do str_replace("\n", ", ", ...) on the multi-line output, because that breaks where line-break semantics differ.

Explicit unknown-country fallback

public function format(Address $address, string $style = 'multi_line'): string
{
    if (! $this->isSupported($address->country_code)) {
        Log::warning('Unsupported country for address formatting', [
            'country'  => $address->country_code,
            'order_id' => $address->order_id ?? null,
        ]);

        return $this->fallbackFormat($address); // your own conservative template
    }

    return AddressFormat::format($this->toComponents($address), $style);
}

Never let the package guess. A plausible-but-wrong template produces a deliverable-looking label that fails downstream — the worst failure mode, because it is silent.

Golden-file tests per market

it('formats correctly for :dataset market', function (string $country, array $components, string $expected) {
    expect(app(AddressFormatter::class)->format(Address::fromArray($components + ['country_code' => $country])))
        ->toBe($expected);
})->with('markets');

Commit fixtures for every supported market. These are your regression suite, your 2.0.0 upgrade gate, and your documentation when upstream goes quiet. They are the most durable artefact you will produce with this package.

Carrier / label integration

Treat the formatter as one input to the label payload, not the payload itself. Carriers typically want structured fields, not concatenated strings — use the package for the human-readable part of the label and confirmation, and pass structured fields to the carrier API. Where a carrier genuinely demands a formatted string, keep that per-carrier mapping in the adapter, not scattered across jobs.


Gotchas and Tips

"Parse" in the description is misleading — it does not parse

This is the biggest trap. The package is a formatter over already-structured fields, not a free-text parser. There is no "123 Main St, Springfield" → structured components path here. If your input is a single textarea, you need a parser or a paid verification vendor — this library does not solve that, and you'll discover it mid-sprint at the worst possible moment. Confirm your ingestion path produces structured fields before committing.

Silent misformatting beats errors, and that's the problem

A mismatched array key isn't an exception — it's a missing line. 2.0.0 adds no schema validation to catch it. Symptoms to recognise:

  • An address renders without a city or postcode for one market only.
  • Trailing or doubled separators where a component is null.
  • A US-looking address appearing on a DE invoice.

Mitigate with the value object, upstream validation (required_if rules per country), and per-market tests. Do not rely on the library to complain.

It formats by country code, not by locale

Chasing per-locale output (French-language invoice for a German address) is your job, not the package's. It picks a template from the country code. Field labels ("Postal code" vs "PLZ" vs "〒") are not in scope either — you own the form layout and labels.

2.0.0 is a breaking release with no functional change

The diff is coding standards plus the PHP 8.2 floor. There is no new capability and no country-metadata refresh. Consequently:

  • There is no feature reason to upgrade — do it when your runtime already forces it.
  • A standards refactor can still shift edge-case output. Capture golden files on 1.x across all supported markets and require byte-identical results before accepting 2.0.0.
  • Sequence the PHP upgrade ahead of the package upgrade. The runtime is the gate, not the library.

Config caching will bite you

If you override templates in config/address-format.php and run php artisan config:cache in deploy, the cached config is what ships. Adding a market means config:cache must re-run — put it explicitly in the deploy script. A "template not taking effect" bug is almost always a stale config cache. Also note in your runbook that the PHP floor changed at 2.0.0, so nobody downgrades the package later to fit an older runtime.

Templates are static data and will drift

Country metadata changes: postal-code patterns, administrative-area naming, new subdivisions. 2.0.0 shipped no refresh, and the project is single-maintainer. Budget a small recurring allowance to review and patch templates for markets you care about. Because it's BSD-2-Clause, forking the config is trivial and legitimate — treat the templates as vendored data, not an upstream responsibility. Decide now who owns that patch if upstream goes quiet.

Non-Latin and RTL are not guaranteed

Japanese, Arabic, Hebrew, and Cyrillic-script addresses may render in the wrong visual order or with incorrect line handling depending on template and rendering target. PDF generation (DomPDF, wkhtmltopdf, Browsershot) each handle RTL differently. Test the actual output artefact — the string can be right in a unit test while the PDF is wrong.

Pin the version explicitly

"torann/address-format": "2.0.*" or "^2.0", chosen deliberately per service. Do not let the resolver decide. Record a sunset expectation for any service that cannot move past 1.x — there will be no maintenance branch and no backported fixes.

Watch the real signal, not the library

Instrument downstream: undeliverable shipments, failed address verification, support tickets mentioning address errors. The package won't tell you it produced bad output, so the delivery pipeline is your monitor.

Keep commerceguys/addressing on the bench

If you later need CLDR-backed coverage, active maintenance, and community validation, the migration should cost you one adapter class — provided you resisted putting the facade in controllers and Blade. That resistance is the whole point of the adapter, and it costs about an hour up front.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core