feft/address-bundle
feft/address-bundle provides reusable address handling for PHP applications, offering models and utilities to store, format, and manage postal addresses. Integrate consistent address data into forms and backend workflows.
This package is a framework-agnostic address library that happens to have been born inside Symfony 2.8 — it ships no Laravel service provider, config file, or facade. Treat it as a plain Composer dependency you wrap yourself.
composer require <vendor>/<package>
php -r "require 'vendor/autoload.php'; echo 'autoload ok';"
Address, Country, Subdivision, and formatter/validator classes first — that is the whole public surface. There is no Laravel docs section to hunt for.use ...\Address;
$address = new Address();
$address->setCountryCode('US');
$address->setAdministrativeArea('CA');
$address->setLocality('Mountain View');
$address->setPostalCode('94043');
$address->setAddressLine1('1600 Amphitheatre Pkwy');
If you only need one country's formatting, don't install this; a Blade partial is cheaper.
Wrap it once, inject everywhere. Create app/Support/Address/AddressFactory.php and an AddressFormatter decorator. Controllers, mailables, and API resources depend on your wrapper, never on the vendor namespace. This keeps you safe if the package stalls (a real risk with a v1.0 rooted in the Symfony 2.8 era).
Bind in a service provider. Even though the package has none, register your wrapper as a singleton so the country/subdivision definition data is loaded once per request rather than per address:
$this->app->singleton(AddressFormatter::class, fn () => new AddressFormatter());
Store structured, format on read. Persist country_code, administrative_area, locality, dependent_locality, postal_code, sorting_code, and two address lines as separate nullable columns. Never store the formatted string as the source of truth — you cannot reliably parse it back, and formatting rules change with library data updates. Format in an accessor or API resource.
Validate per-country, not globally. Postal code rules are country- (and often subdivision-) specific. Use the package's postal code validator in a custom Laravel rule:
public function passes($attribute, $value)
{
return $this->validator->validatePostalCode($value, $this->country, $this->subdivision);
}
Pass the country from the same request payload; validating a postal code without a country is meaningless.
Dynamic address forms. Expose a JSON endpoint returning the field order and required fields for the selected country, then render inputs conditionally in your Livewire/Vue form. Countries without an administrative area (or without postal codes — e.g. parts of the Gulf, Ireland's Eircode quirks) should hide those inputs rather than reject empty values.
Localized output. Wrap presentation in the formatter with the user's locale so field labels and ordering follow local convention. Keep a raw toArray() path for API consumers who want discrete fields.
Integration tips.
Country codes are strict ISO 3166-1 alpha-2, uppercase. 'us' will fail lookups or silently produce no formatting. Normalize with strtoupper() at the boundary, ideally in a form request's prepareForValidation().
Subdivision codes are ISO 3166-2 — prefixed with the country. 'US-CA', not 'CA'. Mixing the two is the most common source of "why is my state missing" bugs. Store the full code and split only for display.
Everything must be nullable. The address format for a country lists exactly the fields it uses; fields absent from that country's format should be null in your model, not ''. Empty strings defeat the formatter's omission logic and leak blank lines with stray commas into PDFs and emails.
Formatting is not validation. A successfully formatted address can still be undeliverable. The library gives you structure and rule-based checks, not deliverability — don't promise customers their address is "verified."
Postal code validation needs the country, and often the subdivision. A validator call without subdivision context will pass things it shouldn't for countries like Canada or the UK. Wire both through from the form.
Data staleness. Country/subdivision/postal patterns are bundled with the release. New subdivisions appear, postal formats change (Ireland's Eircode being the classic example). Pin a version, and schedule a periodic dependency bump review rather than assuming correctness forever.
Symfony 2.8 heritage — check the constraints before you commit. A v1.0 cut from that era may declare php: >=5.5 while relying on internals or deprecations removed in PHP 8.x, and it may pull a symfony/* sub-dependency that conflicts with a modern Laravel skeleton. Run composer why-not on any conflict, and smoke-test address formatting in CI on your actual PHP matrices. If a legacy constraint blocks you, composer require --ignore-platform-req is not the fix — wrap the functionality behind your own interface so you can swap to a maintained fork or a first-party replacement later.
No translation of data. Country and subdivision names come back in one language unless you drive the intl-backed path and have the intl extension installed. Add that to your Dockerfile and CI image, and add a test asserting a non-English name resolves — intl missing fails quietly in some setups.
Extension point. The address format definitions are the intended seam: if your business needs a custom line order (mailing labels, shipping manifests), consume the format definition and render yourself rather than post-processing the formatted string with regexes. Post-processing formatted output is the one thing that reliably breaks on version bumps.
Debugging checklist when output looks wrong: (1) country code uppercase and known, (2) subdivision code includes the country prefix, (3) blank fields are null not '', (4) intl loaded, (5) formatter locale explicitly passed — the default may not be your user's.
How can I help you explore Laravel packages today?