Query scopes
The trait adds query scopes to filter and order owner models by their attributes:
Product::query()->whereAttribute('color', 'white')->get();
Product::query()->whereAttribute('rating', 5)->get(); // typed equality
Product::query()->whereAttributeIn('rating', [3, 5])->get();
Product::query()->whereHasAttribute('on_sale')->get();
Product::query()->whereDoesntHaveAttribute('on_sale')->get();
Product::query()->whereAttributeBetween('rating', 2, 4)->get(); // numeric range
Product::query()->whereAttributeBetween('published_at', $from, $to)->get(); // chronological
Product::query()->whereAttributeNull('note')->get(); // present, value NULL
Product::query()->whereAttributeNotNull('note')->get();
Product::query()->orderByAttribute('rating', 'desc')->get(); // 10, 9, 2 — numbers sort numerically| Scope | Keeps owners whose attribute… |
|---|---|
whereAttribute($name, $value) | equals the value — a typed comparison (see below). |
whereAttributeIn($name, array $values) | equals one of the values; a null in the list matches a stored null. |
whereAttributeBetween($name, $min, $max) | falls within the inclusive bounds — numerically for numbers, chronologically for datetimes, as text otherwise. |
whereAttributeNull($name) | is present with a NULL value. |
whereAttributeNotNull($name) | is present with a non-NULL value. |
whereHasAttribute($name) | is present. |
whereDoesntHaveAttribute($name) | is absent. |
orderByAttribute($name, $direction = 'asc') | — orders owners by the value (asc or desc): numbers numerically, other types by their storage text. |
Combining scopes
Scopes are ordinary Eloquent scopes, so they chain with each other and with the rest of the query builder:
$products = Product::query()
->whereAttribute('color', 'white')
->whereHasAttribute('on_sale')
->whereAttributeBetween('published_at', now()->subMonth(), now())
->orderByAttribute('rating', 'desc')
->paginate(20);How values are compared
Comparisons are typed. The value you pass is taken in the attribute’s defined type when it has a definition — so a request string “5” finds a stored integer 5 — otherwise in its own PHP type, so an integer 5 does not match a stored string “5”. Integers and floats form one numeric family (10 matches 10.0).
Numbers compare and sort numerically, datetimes chronologically — they are stored in UTC, so bounds in any timezone work — and strings lexicographically. orderByAttribute() accepts asc or desc; anything else sorts ascending.
Encrypted values cannot be matched, ranged or sorted by these scopes because their ciphertext is non-deterministic; the presence and null scopes still work. Soft-deleted attributes are ignored by every scope.
Attribute model scopes
The Attribute model itself exposes forName, forOwner and ofType scopes for querying the rows directly:
use RoundlyConsulting\Attributes\Enums\AttributeType;
use RoundlyConsulting\Attributes\Models\Attribute;
Attribute::query()->forName('color')->count(); // rows named color, any owner
Attribute::query()->forOwner($product)->get(); // every row of one owner
Attribute::query()->ofType(AttributeType::Integer)->get(); // rows by stored typeShow 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.