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 Bundle Laravel Package

sylius/addressing-bundle

Sylius AddressingBundle provides sensible, fully customizable models and forms for managing addresses, countries, provinces, and flexible zones that group geographical areas for tax and shipping rules in ecommerce applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: you cannot composer require this into a Laravel app. It is a Symfony2 bundle — Bundle class, DI service definitions, Doctrine ORM mappings, Symfony Form types, Twig views. There is no service provider, no Eloquent, no config publishing, and the repo is explicitly [READ-ONLY] (the code was folded into Sylius core). Treat it as a reference implementation you port by hand, not a dependency. The only things you consume are the domain model and the zone-matching semantics.

Where to look in the source, in order:

  1. Models/ — Address, AddressInterface, Country, Province, Zone, ZoneMember. These define the entity boundaries and the fields you will recreate as migrations.
  2. Resources/config/doctrine/*.orm.xml — the actual column types, nullability, and foreign keys. This is your schema spec.
  3. Form/Type/ — the bundled address/country/province forms; useful as a baseline for checkout field requirements and as a checklist of validation rules (postcode, province requirement, etc.).
  4. Zone matching code — small, relational, and the genuinely valuable part.

First concrete use case (a day of work): stand up three migrations — countries, provinces, zones + zone_members — with Eloquent models mirroring the Doctrine relationships. Seed ISO 3166 data from a maintained source (not the bundle's fixtures, which are stale). Then write a ~40-line ZoneMatcher service that takes an address and returns the zones it belongs to by country and/or province. That service is the whole payoff: it decouples "where is this address" from "what tax/shipping rule applies."

Implementation Patterns

Port the schema, not the ORM. Translate the Doctrine mappings into Laravel migrations directly:

  • countries: id, code (ISO alpha-2), name, timestamps. Keep the code as the stable business key.
  • provinces: id, country_id (FK), code, name. Nullable in spirit — many countries have no subdivisions, so don't force a province on every address.
  • zones: id, name, type/scope if you need to distinguish shipping vs. tax zones.
  • zone_members: id, zone_id, nullable country_id, nullable province_id. A membership row targets either a country or a province — model this as two nullable FKs with a check that exactly one is set, rather than a polymorphic morph, so joins stay simple and indexed.
  • addresses: id, country_id, province_id (nullable), first_name, last_name, street, city, postcode, phone, plus a type discriminator if you follow the bundle's "split into multiple address models" pattern.

Zone matching as a domain service, not model scope creep. Implement it as a dedicated class (App\Services\ZoneMatcher) with one method: given an Address, return a collection of matching Zone IDs. Query zone_members where country_id = address.country_id OR province_id = address.province_id, distinct the zone IDs, done. Keeping this out of the model means you can cache it (Cache::remember keyed on country/province pair) and swap the implementation later without touching call sites.

Extend the address model with columns, not subclassing. The bundle lets you "add custom fields to your address entity or split it into several models for different address types." In Laravel, prefer the pragmatic version: real columns for anything you filter/sort/report on (company, tax_id, delivery_notes), and a JSON meta column only for genuinely freeform, never-queried attributes. Splitting billing and shipping into separate tables is almost always a mistake — add an addresses.type enum or a billing_address_id/shipping_address_id pair on the owning order/customer instead.

Billing vs. shipping. The bundle's guidance ("split into several models to handle different address types") maps cleanly to: one addresses table, two foreign keys on the order (billing_address_id, shipping_address_id). This keeps de-duplication trivial and avoids parallel validation rules for two nearly identical entities.

Reuse the form logic as validation rules. Translate the Symfony Form constraints into Laravel FormRequest rules — the bundle's forms encode real-world quirks (postcode formats, required-vs-optional province per country) that are easy to get wrong. Mirror those into a StoreAddressRequest and, importantly, make province/state required conditional on the selected country.

Reference data as a seeded table, versioned by migration. Import ISO 3166 countries and subdivisions from a maintained dataset into a seeder or CSV-backed migration. Keep it separate from addresses so you can refresh it without touching user data.

Gotchas and Tips

  • The dependency fight is a hard stop. The bundle's composer.json constraints on symfony/framework-bundle, doctrine/orm, and php will collide with a modern Laravel install (Symfony 6/7). Don't spike it expecting it to work — verify constraints once, then move on.
  • "Zones" ≠ tax engine. The bundle models membership and matching only. It does not compute rates. If stakeholders hear "zones are supported," clarify that the rate logic on top is entirely yours to build — and that's the larger half of the work.
  • Don't trust the shipped reference data. Fixtures are stale and no one is merging ISO updates into a read-only repo. Use a maintained country/subdivision dataset and plan a refresh cadence you own.
  • Country codes, not names, as keys. Store ISO alpha-2 (or alpha-3, pick one and be consistent) on countries and reference by ID from addresses. Comparing on country name strings is a classic source of mismatches ("USA" vs "United States" vs "US").
  • Nullable province is a real state. Enforce the province requirement per country at validation time, not schema time. Countries like Singapore or UAE have no meaningful subdivision; a non-null province_id will force garbage data.
  • Index zone_members on both country_id and province_id (plus zone_id). Zone matching runs on every cart/checkout price and shipping calculation — an unindexed scan there is a latency bug waiting to surface under load.
  • Cache zone matches per (country, province) pair. The match result is stable unless your zone definitions change; invalidate on zone edits. This turns a per-request query into a lookup for the hot path.
  • Watch for N+1 on address display. address.country and address.province are two lazy relations rendered on every checkout summary — eager-load both (with(['country', 'province'])) on the order/address query.
  • The bundle's "extend with your own models for more levels" is a warning, not an invitation. The generic two-level Country→Province hierarchy covers ~95% of use cases. Deeper geographic nesting (region → district → …) is a genuine modeling decision with its own cost — don't build it until a market requirement forces it.
  • Extension point worth keeping: a small ZoneMatcher interface (one method, match(Address): Collection) so a future rules engine — tax-by-region, shipping-by-band — can be layered on without rewriting call sites. That interface is the portable idea; the bundle's DI config is not.
  • Debugging tip: when a zone unexpectedly matches or doesn't, dump the resolved (country_id, province_id) pair from the address first — the bug is almost always at the address→geography mapping, not in the zone membership query itself.
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.
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
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums