Fluent builder
Addresses::for($model)->new() and $model->newAddress() both return a PendingAddress. Chain the fields, then save() persists the address and returns it. Country codes are normalised automatically, and an address with no type() gets the configured default_type:
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Facades\Addresses;
Addresses::for($payer)->new()
->type(AddressType::Office)
->name('HQ')
->primary()
->in('Bratislava')->at('Somewhere 1')->postalCode('81101')->country('sk')
->meta(['floor' => 3])
->save();
// or from the model
$payer->newAddress()
->type(AddressType::Home)
->in('Košice')->at('Main 2')->postalCode('04001')->country('SK')
->save();Builder methods
| Method | Effect |
|---|---|
type(AddressType $type) | Address type — without it, the configured default_type. |
name(string $name) | Optional label, e.g. HQ; a blank name is stored as null. |
in(string $city) | City — required. |
at(string $street) | Street line — required. |
postalCode(string $postalCode) | Postal code — required. |
country(string $country) | ISO country code — required; normalised on save. |
primary(bool $isPrimary = true) | Make it the primary address of its type; siblings are demoted on save, in the same transaction. |
meta(iterable $meta) | Arbitrary extras, wrapped in a Collection and stored as JSON. |
save(): Address | Checks the four required fields, adds the address through the owner’s address book and returns it. |
Required fields
City, street, postal code and country are required. Calling save() without all four — or with a blank value — throws IncompleteAddressException naming every missing field:
use RoundlyConsulting\Addresses\Exceptions\IncompleteAddressException;
try {
$customer->newAddress()->in('Bratislava')->save();
} catch (IncompleteAddressException $e) {
$e->getMessage();
// 'The address is missing required field(s): [street, postalCode, country].'
}What save() does
The collected fields pass through AddressData::make() — strings are trimmed, a blank name becomes null, the country is normalised and a missing type becomes default_type — and save() adds the result through the owner’s address book, so Addresses::fake() sees it too. CreateAddressAction inserts the row on the owner’s morph relation and, when primary() was set, promotes it in the same transaction, demoting the other addresses of that type. Then AddressCreated is dispatched, followed by PrimaryAddressChanged for a promotion.
Addresses::for() accepts any Eloquent model — the HasAddresses trait is only needed for the model-side helpers.
From a request
use App\Models\Customer;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Http\Resources\AddressResource;
final class CustomerAddressController
{
public function store(Request $request, Customer $customer): AddressResource
{
$data = $request->validate([
'type' => ['required', Rule::enum(AddressType::class)],
'city' => ['required', 'string', 'max:255'],
'street' => ['required', 'string', 'max:255'],
'postal_code' => ['required', 'string', 'max:255'],
'country' => ['required', 'string', 'min:2', 'max:3'],
'primary' => ['boolean'],
]);
$address = $customer->newAddress()
->type(AddressType::from($data['type']))
->in($data['city'])
->at($data['street'])
->postalCode($data['postal_code'])
->country($data['country']) // 'sk' is stored as 'SK'
->primary($request->boolean('primary'))
->save();
return AddressResource::make($address);
}
}Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.