WithAddresses Trait
The WithAddresses
trait adds multiple polymorphic address relationships to your models, allowing them to have multiple associated addresses with complete Indonesian administrative region data.
Namespace
php
Creasi\Nusa\Models\Concerns\WithAddresses
Usage
Basic Implementation
php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Creasi\Nusa\Models\Concerns\WithAddresses;
class Company extends Model
{
use WithAddresses;
protected $fillable = [
'name',
'description'
];
}
Database Setup
The trait uses Laravel Nusa's built-in address table. Ensure you've published and run the migrations:
bash
php artisan vendor:publish --tag=creasi-migrations
php artisan migrate
Features
Multiple Address Relationships
Access all associated addresses:
php
$company = Company::find(1);
$addresses = $company->addresses;
foreach ($addresses as $address) {
echo "Type: {$address->type}";
echo "Address: {$address->address_line}";
echo "Location: {$address->village->name}, {$address->regency->name}";
}
Creating Multiple Addresses
php
$company = Company::find(1);
// Headquarters
$company->addresses()->create([
'type' => 'headquarters',
'address_line' => 'Jl. Sudirman No. 123',
'province_code' => '31',
'regency_code' => '31.71',
'district_code' => '31.71.01',
'village_code' => '31.71.01.1001'
]);
// Branch office
$company->addresses()->create([
'type' => 'branch',
'address_line' => 'Jl. Malioboro No. 456',
'province_code' => '34',
'regency_code' => '34.71',
'district_code' => '34.71.02',
'village_code' => '34.71.02.1005'
]);
// Warehouse
$company->addresses()->create([
'type' => 'warehouse',
'address_line' => 'Jl. Industri No. 789',
'province_code' => '33',
'regency_code' => '33.74',
'district_code' => '33.74.05',
'village_code' => '33.74.05.1010'
]);
Common Use Cases
1. Multi-Location Businesses
php
class Company extends Model
{
use WithAddresses;
protected $fillable = ['name', 'description'];
public function getHeadquartersAttribute()
{
return $this->addresses()->where('type', 'headquarters')->first();
}
public function getBranchesAttribute()
{
return $this->addresses()->where('type', 'branch')->get();
}
public function getWarehousesAttribute()
{
return $this->addresses()->where('type', 'warehouse')->get();
}
public function scopeWithLocationsIn($query, $provinceCode)
{
return $query->whereHas('addresses', function ($q) use ($provinceCode) {
$q->where('province_code', $provinceCode);
});
}
public function getLocationCountAttribute()
{
return $this->addresses()->count();
}
public function getCoverageAreasAttribute()
{
return $this->addresses()
->with('province')
->get()
->pluck('province.name')
->unique()
->values();
}
}
// Usage
$company = Company::with(['addresses.village.district.regency.province'])->first();
echo "Headquarters: " . $company->headquarters?->address_line;
echo "Branches: " . $company->branches->count();
echo "Coverage: " . $company->coverage_areas->implode(', ');
2. Customer Management
php
class Customer extends Model
{
use WithAddresses;
protected $fillable = [
'name',
'email',
'phone'
];
public function getHomeAddressAttribute()
{
return $this->addresses()->where('type', 'home')->first();
}
public function getOfficeAddressAttribute()
{
return $this->addresses()->where('type', 'office')->first();
}
public function getShippingAddressesAttribute()
{
return $this->addresses()->where('type', 'shipping')->get();
}
public function addShippingAddress(array $addressData)
{
return $this->addresses()->create(array_merge($addressData, [
'type' => 'shipping'
]));
}
public function getPreferredShippingAddressAttribute()
{
return $this->addresses()
->where('type', 'shipping')
->where('is_default', true)
->first() ?? $this->home_address;
}
public function setDefaultShippingAddress($addressId)
{
// Remove default from all shipping addresses
$this->addresses()
->where('type', 'shipping')
->update(['is_default' => false]);
// Set new default
return $this->addresses()
->where('id', $addressId)
->where('type', 'shipping')
->update(['is_default' => true]);
}
}
// Usage
$customer = Customer::first();
$customer->addShippingAddress([
'address_line' => 'Jl. Delivery No. 123',
'village_code' => '33.74.01.1001',
'district_code' => '33.74.01',
'regency_code' => '33.74',
'province_code' => '33'
]);
$shippingAddress = $customer->preferred_shipping_address;
3. Event Management
php
class Event extends Model
{
use WithAddresses;
protected $fillable = [
'title',
'description',
'event_date'
];
protected $casts = [
'event_date' => 'datetime'
];
public function getVenuesAttribute()
{
return $this->addresses()->where('type', 'venue')->get();
}
public function getAccommodationsAttribute()
{
return $this->addresses()->where('type', 'accommodation')->get();
}
public function addVenue(array $venueData)
{
return $this->addresses()->create(array_merge($venueData, [
'type' => 'venue'
]));
}
public function addAccommodation(array $accommodationData)
{
return $this->addresses()->create(array_merge($accommodationData, [
'type' => 'accommodation'
]));
}
public function getLocationSummaryAttribute()
{
$venues = $this->venues;
if ($venues->isEmpty()) {
return 'Venues TBA';
}
$cities = $venues->map(function ($venue) {
return $venue->regency->name;
})->unique();
return $cities->count() === 1
? $cities->first()
: $cities->count() . ' cities';
}
}
4. Logistics Management
php
class LogisticsProvider extends Model
{
use WithAddresses;
protected $fillable = [
'name',
'service_type'
];
public function getWarehousesAttribute()
{
return $this->addresses()->where('type', 'warehouse')->get();
}
public function getDistributionCentersAttribute()
{
return $this->addresses()->where('type', 'distribution_center')->get();
}
public function getServiceAreasAttribute()
{
return $this->addresses()
->with('province')
->get()
->groupBy('province.name')
->map(function ($addresses, $provinceName) {
return [
'province' => $provinceName,
'locations' => $addresses->count(),
'types' => $addresses->pluck('type')->unique()->values()
];
});
}
public function canServeLocation($provinceCode, $regencyCode = null)
{
$query = $this->addresses()->where('province_code', $provinceCode);
if ($regencyCode) {
$query->where('regency_code', $regencyCode);
}
return $query->exists();
}
public function getNearestFacility($provinceCode, $regencyCode, $type = null)
{
$query = $this->addresses()
->where('province_code', $provinceCode)
->where('regency_code', $regencyCode);
if ($type) {
$query->where('type', $type);
}
return $query->first();
}
}
// Usage
$provider = LogisticsProvider::first();
$canServe = $provider->canServeLocation('33', '33.74');
$warehouse = $provider->getNearestFacility('33', '33.74', 'warehouse');
$serviceAreas = $provider->service_areas;
Advanced Usage
Address Type Management
php
class AddressableModel extends Model
{
use WithAddresses;
const ADDRESS_TYPES = [
'home' => 'Home Address',
'office' => 'Office Address',
'shipping' => 'Shipping Address',
'billing' => 'Billing Address',
'warehouse' => 'Warehouse',
'branch' => 'Branch Office',
'headquarters' => 'Headquarters'
];
public function getAddressesByTypeAttribute()
{
return $this->addresses->groupBy('type');
}
public function getAddressTypesAttribute()
{
return $this->addresses->pluck('type')->unique()->values();
}
public function hasAddressType($type)
{
return $this->addresses()->where('type', $type)->exists();
}
public function getAddressByType($type)
{
return $this->addresses()->where('type', $type)->get();
}
public function removeAddressType($type)
{
return $this->addresses()->where('type', $type)->delete();
}
}
Bulk Address Operations
php
class AddressManager
{
public static function bulkCreateAddresses($model, array $addressesData)
{
$addresses = collect($addressesData)->map(function ($data) use ($model) {
return array_merge($data, [
'addressable_id' => $model->id,
'addressable_type' => get_class($model),
'created_at' => now(),
'updated_at' => now()
]);
});
return \DB::table('addresses')->insert($addresses->toArray());
}
public static function syncAddresses($model, array $addressesData)
{
// Delete existing addresses
$model->addresses()->delete();
// Create new addresses
return static::bulkCreateAddresses($model, $addressesData);
}
public static function getLocationStatistics($modelClass)
{
return $modelClass::with('addresses.province')
->get()
->flatMap(function ($model) {
return $model->addresses;
})
->groupBy('province.name')
->map(function ($addresses, $provinceName) {
return [
'province' => $provinceName,
'count' => $addresses->count(),
'types' => $addresses->pluck('type')->unique()->values()
];
});
}
}
Validation
Multiple Address Validation
php
class CompanyAddressRequest extends FormRequest
{
public function rules()
{
return [
'addresses' => 'required|array|min:1',
'addresses.*.type' => 'required|string|in:headquarters,branch,warehouse',
'addresses.*.address_line' => 'required|string|max:255',
'addresses.*.village_code' => 'required|exists:nusa.villages,code',
'addresses.*.postal_code' => 'nullable|string|size:5'
];
}
public function messages()
{
return [
'addresses.*.type.in' => 'Address type must be headquarters, branch, or warehouse.',
'addresses.*.village_code.exists' => 'The selected village is invalid.'
];
}
}
Unique Address Type Validation
php
class CustomerAddressRequest extends FormRequest
{
public function rules()
{
$customerId = $this->route('customer')->id ?? null;
return [
'type' => [
'required',
'string',
Rule::unique('addresses')
->where('addressable_type', Customer::class)
->where('addressable_id', $customerId)
->ignore($this->address)
],
'address_line' => 'required|string|max:255',
'village_code' => 'required|exists:nusa.villages,code'
];
}
}
Performance Tips
1. Eager Loading
php
// Good
$companies = Company::with([
'addresses.village.district.regency.province'
])->get();
// Bad - N+1 queries
$companies = Company::all();
foreach ($companies as $company) {
foreach ($company->addresses as $address) {
echo $address->village->name; // Multiple queries
}
}
2. Selective Loading by Type
php
$companies = Company::with([
'addresses' => function ($query) {
$query->where('type', 'headquarters')
->with('village.regency.province');
}
])->get();
3. Counting Addresses
php
$companies = Company::withCount([
'addresses',
'addresses as branches_count' => function ($query) {
$query->where('type', 'branch');
},
'addresses as warehouses_count' => function ($query) {
$query->where('type', 'warehouse');
}
])->get();
Related Documentation
- WithAddress Trait - For single address support
- Address Model - Complete Address model documentation
- Address Management Guide - Complete address functionality guide
- Address Forms Example - Building address forms