Removing & syncing
Removals soft-delete by default; pass forceDelete: true to delete permanently:
use RoundlyConsulting\Attributes\Facades\Attributes;
Attributes::for($product)->forget('color'); // soft delete; returns the count removed
Attributes::for($product)->forget(['color', 'size'], forceDelete: true); // permanently
Attributes::for($product)->forgetExcept(['color']); // list<string> of the removed namesThe model methods are shorthand for forget() and forgetExcept() through the same manager:
$product->syncAttributeMeta('color', collect(['is_pretty' => 'yes']));
$product->detachAttribute('color');
$product->detachAttribute('color', forceDelete: true);
$product->detachAttributes(['color', 'size']);
$product->destroyAttributesExcept(['color']);
$product->destroyAttributes(['price']);Syncing
sync() makes the model’s attributes match the given set exactly — attributes outside the set are removed, and each given value is attached or updated:
use RoundlyConsulting\Attributes\Facades\Attributes;
Attributes::for($product)->sync([
'color' => 'black',
'size' => 'large',
]); // exactly color and size remain
Attributes::for($product)->sync(['color' => 'black'], forceDelete: true); // hard-delete the rest
Attributes::for($product)->sync(['color' => 'black'], meta: ['color' => ['hex' => '#000']]);
Attributes::for($product)->sync([]); // remove every attribute
$product->syncAttributes(['color' => 'black']); // model shorthandBehaviour reference
- forget() — and detachAttribute(), detachAttributes(), destroyAttributes() — removes the named attributes and returns how many; AttributeDetached is dispatched once per name that was actually attached (a name that never was is a no-op), and with history enabled a detached revision is recorded.
- forgetExcept() — and destroyAttributesExcept() — removes every attribute not in the keep-list through the same path, so each removal fires AttributeDetached and records a detached revision. It returns the removed names.
- sync() — and syncAttributes() — validates every given value before anything is detached or written, then removes attributes outside the set the same way and attaches each value, all in one transaction, and dispatches AttributesSynced. It returns the attached Attribute models.
- A force-deleting forgetExcept() or sync() also purges previously soft-deleted extras.
- A soft-deleted value gives up its unique slot, so another owner can take it. Restoring the row with $attribute->restore() takes the slot back — or throws DuplicateAttributeValueException and leaves the row trashed when another owner holds the value by now.
Soft-deleted attributes stay recoverable until attributes:prune removes them permanently (see Artisan commands). Attaching a name again after a soft delete restores its row with a fresh value and meta — the old meta is not revived.
Deleting the owner
Deleting an owner for good — a model without SoftDeletes, or forceDelete() — removes all its attribute rows, soft-deleted ones included, and frees its unique values. It goes through Attributes::for($owner)->forget() with forceDelete, so AttributeDetached fires once per attached name, history records detached revisions and Attributes::fake() records the forget. Recorded revisions are never deleted — they stay as the audit trail. Soft-deleting the owner keeps its attributes, so restoring it brings them back. Set delete_with_owner (ATTRIBUTES_DELETE_WITH_OWNER) to false to keep the rows on every delete and clean up yourself.
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.