laravel-address maintained by hasyirin
Laravel Address
A Laravel package for managing addresses with polymorphic relationships and hierarchical geographical data (countries, states, districts, subdistricts, and post offices).
Comes with built-in geographical data for Malaysia and a seeder command to populate reference tables.
Requirements
- PHP 8.4+
- Laravel 11 or 12
Installation
Install the package via Composer:
composer require hasyirin/laravel-address
Publish and run the migrations:
php artisan vendor:publish --tag="laravel-address-migrations"
php artisan migrate
Optionally publish the config file:
php artisan vendor:publish --tag="laravel-address-config"
Seeding Geographical Data
Seed countries, states, districts, subdistricts, and post offices:
php artisan address:seed
The command loads data from the package's data/ directory. To use your own data, place JSON files in your application's base_path('data') directory following the same structure.
Usage
Preparing Your Model
Add the InteractsWithAddresses trait to any model that should have addresses:
use Hasyirin\Address\Concerns\InteractsWithAddresses;
use Hasyirin\Address\Contracts\Addressable;
class User extends Model implements Addressable
{
use InteractsWithAddresses;
}
Creating Addresses
$user->address()->create([
'type' => 'primary',
'line_1' => '123 Main Street',
'line_2' => 'Suite 4B',
'line_3' => 'Taman Example',
'postcode' => '50000',
'country_id' => $country->id,
'state_id' => $state->id,
'post_office_id' => $postOffice->id,
'latitude' => 3.1390,
'longitude' => 101.6869,
'properties' => ['notes' => 'Front gate access'],
]);
Using the Factory
The Address model includes a factory for testing:
use Hasyirin\Address\Models\Address;
Address::factory()
->for($user, 'addressable')
->create([
'type' => 'primary',
'line_1' => 'No. 1, Jalan Test',
'postcode' => '40000',
]);
Retrieving Addresses
// Primary address (returns a default empty instance if none exists)
$user->address;
// All addresses
$user->addresses;
// Address by type
$user->getAddress('billing');
Querying by Type
The ofType scope accepts a string, array, or enum:
use Hasyirin\Address\Models\Address;
Address::ofType('shipping')->get();
Address::ofType(['billing', 'shipping'])->get();
Formatting
// Single-line comma-separated string
$address->formatted();
// "123 Main Street, Suite 4B, Taman Example, 50000, Kuala Lumpur, Selangor, Malaysia"
// Exclude state or country
$address->formatted(state: false);
$address->formatted(country: false);
// Uppercase
$address->formatted(capitalize: true);
HTML Rendering
// Multi-line with <p> tags
$address->render();
// Inline comma-separated
$address->render(inline: true);
// With options
$address->render(state: false, country: false, capitalize: true, margin: 1);
Copying an Address
Create an unsaved copy of an address (without the polymorphic relationship or type):
$copy = $address->copy();
$copy->type = 'billing';
$copy->addressable()->associate($anotherModel);
$copy->save();
Configuration
// config/address.php
return [
// Mark a country/state as "local" during seeding
'locality' => [
'country' => null, // e.g. 'MYS'
'state' => null, // e.g. '01'
],
// Override model classes
'models' => [
'address' => \Hasyirin\Address\Models\Address::class,
'country' => \Hasyirin\Address\Models\Country::class,
'state' => \Hasyirin\Address\Models\State::class,
'district' => \Hasyirin\Address\Models\District::class,
'subdistrict' => \Hasyirin\Address\Models\Subdistrict::class,
'post-office' => \Hasyirin\Address\Models\PostOffice::class,
],
// Override table names
'tables' => [
'addresses' => 'addresses',
'countries' => 'countries',
'states' => 'states',
'districts' => 'districts',
'subdistricts' => 'subdistricts',
'post_offices' => 'post_offices',
],
];
Data Structure
The package uses a hierarchical location model:
Country
└── State
├── District
│ └── Subdistrict
└── Post Office (with postcodes)
Each Address belongs to a Country, State, and optionally a PostOffice, and is polymorphically attached to any model via the addressable morph relationship.
Models
| Model | Key Fields |
|---|---|
Country |
code (ISO 3166-1 alpha-3), alpha_2, name, local |
State |
code, name, local, belongs to Country |
District |
code, name, belongs to State |
Subdistrict |
code, name, belongs to District |
PostOffice |
name, postcodes (JSON array), belongs to State |
Address |
type, line_1-line_3, postcode, latitude, longitude, properties (JSON) |
All models use soft deletes.
Extending Models
To use your own model classes, extend the package models and update the config:
use Hasyirin\Address\Models\Address as BaseAddress;
class Address extends BaseAddress
{
// your customizations
}
// config/address.php
'models' => [
'address' => \App\Models\Address::class,
// ...
],
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.