Updating & deleting
Updating
Addresses::for($owner)->update() writes an AddressData onto one of the owner’s addresses in one transaction, promotes it when isPrimary is true, dispatches AddressUpdated (then PrimaryAddressChanged for a promotion) and returns the refreshed model. It runs UpdateAddressAction with the owner, which you can also call directly:
use RoundlyConsulting\Addresses\DataTransferObjects\AddressData;
use RoundlyConsulting\Addresses\Facades\Addresses;
$address = Addresses::for($customer)->update($address, AddressData::make(
city: 'Bratislava',
street: 'Somewhere 5',
postalCode: '81102',
countryIso: 'SK',
// AddressData is a full write — carry over what should not change
name: $address->name,
type: $address->type,
isPrimary: $address->is_primary,
meta: $address->meta,
));The update is a full write: every DTO field replaces the stored value. Leaving out name, type, isPrimary or meta resets them — name and meta to null, type to the configured default_type, and isPrimary to false, which demotes the address — so carry over whatever should not change, as above. Asking a soft-deleted address to become primary throws TrashedAddressException.
The write is compared against the stored row, re-read under a lock, not against the copy you pass: a stale copy still demotes a primary promoted elsewhere, overwrites a column changed behind it with the DTO’s value, and decides a type change from the stored type. Columns the DTO does not carry keep their stored values, unless you changed them on the copy without saving — those changes are written too.
A plain $address->update([...]) still works, but it skips country normalisation, the primary invariant and the AddressUpdated event.
Owner-checked or unscoped
The book’s update() and delete() refuse an address that belongs to another owner with AddressOwnershipException — compared by morph type and key on the stored row, read under a lock, so a forged or stale copy does not get through — and an update whose copy would hand the address to another owner is refused too. Nothing is written. Use them whenever the address id comes from a request. Addresses::update() and Addresses::delete() stay unscoped and take any address, for admin tools, imports and jobs.
Deleting
Addresses::for($owner)->delete() — or the unscoped Addresses::delete() — soft-deletes an address, so an accidental removal can be undone. AddressDeleted is dispatched from the model’s deleted hook, which runs for delete() and forceDelete() alike:
use RoundlyConsulting\Addresses\Address;
use RoundlyConsulting\Addresses\Facades\Addresses;
Addresses::for($customer)->delete($address); // owner-checked soft delete; dispatches AddressDeleted
Addresses::delete($address); // the same, unscoped — any owner's address
$address->restore(); // bring it back (as primary only if the slot is free)
$address->forceDelete(); // permanent removal (dispatches AddressDeleted too)
Address::onlyTrashed()->get(); // what was removed
$customer->addresses()->withTrashed()->get(); // including removed onesDeleting the primary leaves its type without one until you promote another. The deleted address keeps its flag, so restore() brings it back as the primary — but only while no other live primary holds the owner + type slot, and then dispatches PrimaryAddressChanged; otherwise it returns as a plain address (see Primary addresses).
When the owner is deleted
The morph column has no foreign key, so deleting an owner leaves its addresses behind. Delete them per model so AddressDeleted fires for each — a bulk $customer->addresses()->delete() also soft-deletes, but fires no model events:
// addresses are not removed with their owner — there is no foreign key or cascade.
// delete them per model so AddressDeleted fires for each:
$customer->addresses->each->delete();
$customer->delete();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.