Installation:
composer require metamel/laravel-addresses
php artisan vendor:publish --tag=addresses-config
php artisan migrate
addresses and addressables tables exist in your database.First Use Case:
Attach the Addressable trait to a model (e.g., User):
use Metamel\Addresses\Traits\Addressable;
class User extends Model
{
use Addressable;
}
User instance will have an addresses() relationship and addresses() method.Quick Test:
$user = User::find(1);
$user->addresses()->create([
'label' => 'Home',
'street' => '123 Main St',
'city' => 'Anytown',
'postcode' => '12345',
'country' => 'US'
]);
addressables table.Polymorphic Address Management:
User, Vendor, Store) to enable address storage.class Vendor extends Model
{
use Addressable;
}
addresses table with a polymorphic addressable_id and addressable_type.Address Retrieval:
$user->addresses; // Collection of Address models
$user->addresses()->where('label', 'Work')->get();
Validation:
config/addresses.php):
use Metamel\Addresses\Rules\AddressRules;
$validated = $request->validate([
'street' => ['required', new AddressRules\Street],
'city' => ['required', new AddressRules\City],
]);
Geocoding (Optional):
$address = $user->addresses()->create([...]);
$address->geocode(); // Updates lat/long via config service (e.g., Google Maps)
API Responses:
return User::with('addresses')->find(1);
AppServiceProvider:
Address::addHidden(['internal_field']);
Events:
Listen for AddressCreated, AddressUpdated, or AddressDeleted events to trigger notifications or sync external services.
event(new AddressCreated($address));
Scopes: Add global scopes to filter addresses (e.g., active only):
use Metamel\Addresses\Scopes\ActiveScope;
class User extends Model
{
use Addressable;
protected static function booted()
{
static::addGlobalScope(new ActiveScope);
}
}
Testing: Use factories to seed test addresses:
$user = User::factory()->create();
$user->addresses()->createMany([
['label' => 'Home', 'street' => '123 Test St'],
['label' => 'Work', 'street' => '456 Office Ave'],
]);
Migration Conflicts:
addresses or addressables tables, future migrations may fail. Always run php artisan migrate:fresh after manual changes.Polymorphic Relationships:
Addressable trait on a model will break polymorphic queries. Double-check:
// ❌ Fails silently if trait is missing
$user->addresses()->get();
Geocoding Failures:
lat/lng fields will be null. Handle this in your UI:
$address->lat ?? 'N/A'
Soft Deletes:
Address model:
use SoftDeletes;
class Address extends Model
{
use SoftDeletes;
}
Then update the addresses table migration to add deleted_at.Label Uniqueness:
$request->validate([
'label' => ['required', Rule::unique('addresses')->where('addressable_id', $user->id)],
]);
Missing Addresses:
addressable_type matches the model's fully qualified class name (e.g., App\Models\User).addresses() vs. address()).Query Issues:
toSql() to debug raw queries:
$query = $user->addresses()->where('label', 'Home');
dd($query->toSql(), $query->getBindings());
Config Overrides:
config/addresses.php) may override defaults. Review:
'geocoding' => [
'service' => 'google', // or 'openstreetmap'
'api_key' => env('GOOGLE_MAPS_API_KEY'),
],
Custom Address Fields:
Address model to add fields (e.g., floor, unit):
class Address extends \Metamel\Addresses\Models\Address
{
protected $fillable = ['floor', 'unit'];
}
Custom Validation:
AddressRules::extend('postcode', function ($attribute, $value, $fail) {
if (!preg_match('/^\d{5}(-\d{4})?$/', $value)) {
$fail('Invalid US ZIP code.');
}
});
Address Types:
type field to categorize addresses (e.g., "billing", "shipping") and filter:
$user->addresses()->where('type', 'shipping')->get();
Localization:
resources/lang/en/addresses.php:
return [
'label' => 'Address Label',
'street' => 'Street Address',
];
API Resources:
php artisan make:resource AddressResource
Then customize the response:
public function toArray($request)
{
return [
'full_address' => $this->formatFullAddress(),
'distance' => $this->calculateDistance($request->user()),
];
}
How can I help you explore Laravel packages today?