Skip to content

Trait WithAddresses

Trait WithAddresses menambahkan beberapa relasi alamat polimorfik ke model Anda, memungkinkan mereka untuk memiliki beberapa alamat terkait dengan data wilayah administratif Indonesia yang lengkap.

Namespace

php
Creasi\Nusa\Models\Concerns\WithAddresses

Penggunaan

Implementasi Dasar

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'
    ];
}

Pengaturan Database

Trait ini menggunakan tabel alamat bawaan Laravel Nusa. Pastikan Anda telah mempublikasikan dan menjalankan migrasi:

bash
php artisan vendor:publish --tag=creasi-migrations
php artisan migrate

Fitur

Relasi Alamat Ganda

Mengakses semua alamat yang terkait:

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'
]);

Contoh Penggunaan Umum

1. Bisnis Multi-Lokasi

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. Manajemen Pelanggan

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. Manajemen Acara

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. Manajemen Logistik

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;

Penggunaan Lanjutan

Manajemen Tipe Alamat

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();
    }
}

Operasi Alamat Massal

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()
                ];
            });
    }
}

Validasi

Validasi Alamat Ganda

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.'
        ];
    }
}

Validasi Tipe Alamat Unik

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'
        ];
    }
}

Tips Kinerja

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. Memuat Berdasarkan Tipe secara Selektif

php
$companies = Company::with([
    'addresses' => function ($query) {
        $query->where('type', 'headquarters')
              ->with('village.regency.province');
    }
])->get();

3. Menghitung Alamat

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();

Dokumentasi Terkait

Released under the MIT License.