Attaching attributes & metadata
Attributes::for($model)->set() creates the attribute, or updates it in place when the name already exists — each owner holds one row per name, and a detached name gets its row back with a fresh value and meta:
use RoundlyConsulting\Attributes\Facades\Attributes;
// One attribute — creates it, or updates it in place; returns the Attribute model
Attributes::for($product)->set('color', 'white');
Attributes::for($product)->set('color', 'black'); // same row, new value
// Several at once from a name => value map; attributes you don't name are kept
Attributes::for($product)->setMany([
'color' => 'white',
'size' => 'large',
'stock' => 12,
]); // Collection<Attribute>What happens on attach
- Strict mode — with strict on, a name with neither a global nor a model-schema definition throws UnknownAttributeException.
- Validation — a registered name is validated against its type and rules (InvalidAttributeValueException).
- Uniqueness — a unique definition rejects a value another owner already holds (DuplicateAttributeValueException); the unique index settles concurrent writers.
- Persistence — the value is stored in its defined type (else the PHP value’s); encrypted definitions are stored as ciphertext. Without new meta, the stored meta is kept.
- History — with history enabled, an attached or updated revision is recorded.
- Event — AttributeAttached is dispatched once the write’s transaction commits.
setMany() runs the same pipeline for each entry of a name → value map, all or nothing: every value is validated before the first write, and the writes share one transaction.
On the model
attachAttribute() and attachAttributes() are model shorthand for set() and setMany() — they delegate to the same manager and return the model, so calls chain:
// One attribute — creates it, or updates it in place
$product->attachAttribute('color', 'white');
$product->attachAttribute('color', 'black'); // same row, new value
// Several at once from a name => value map
$product->attachAttributes([
'color' => 'white',
'size' => 'large',
'stock' => 12,
]);
// Writers return the model, so calls chain
$product->attachAttribute('on_sale', true)->attachAttribute('discount', 0.15);Metadata
Every attribute can carry a metadata collection — a hex code, a unit, where the value came from. Pass it as an array or a Collection when you set the attribute, or replace it later with meta():
use RoundlyConsulting\Attributes\Facades\Attributes;
// Attach with metadata (an array or a Collection)
Attributes::for($product)->set('color', 'white', meta: ['hex' => '#ffffff']);
// setMany() and sync() take a per-name meta map as their last argument
Attributes::for($product)->setMany(['color' => 'white', 'size' => 'L'], meta: ['color' => ['hex' => '#ffffff']]);
$product->getAttachedAttributeMeta('color'); // Collection: ['hex' => '#ffffff']
// A new value without meta keeps the stored meta
Attributes::for($product)->set('color', 'black');
// Replace the metadata, keep the value
Attributes::for($product)->meta('color', ['hex' => '#f5f5f5', 'source' => 'import']);
// Clear it
Attributes::for($product)->meta('color', null);
// Model shorthand
$product->attachAttribute('color', 'white', collect(['hex' => '#ffffff']));
$product->syncAttributeMeta('color', collect(['hex' => '#f5f5f5']));meta() — and its model shorthand syncAttributeMeta() — replaces the whole metadata collection of the named attribute and leaves its value untouched; if the attribute is not attached yet, it creates the row with a null value. Pass null to clear the metadata. It goes through the same write as a value: strict mode applies, history records a revision and AttributeAttached fires. Since the value is left alone, strict mode is the only check it can fail.
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.