Product Properties
Most E-commerce systems name this technique as "product attributes".
Laravel's Eloquent also names fields of models (entities) as "attributes".
In order to prevent confusion between the two, Vanilo calls these "EAV" style attributes as "properties" instead.
Spree Commerce for example also has this naming: Spree Commerce Product Properties
Overview
Product properties track individual attributes of a product which don’t apply to all products.
Examples of product properties are:
- material of a T-Shirt,
- color of a Smartphone,
- RAM size of a Laptop,
- alcohol % of a Spirit drink,
- whether a Fridge has display.
The property module was created so that properties can be created and assigned to any model, not just products. This description however, will focus on using properties for products.
Defining Properties
Properties are like "color", "material" or "wheel size". To create a new property you have to specify it's name and type.
use Vanilo\Properties\Models\Property;
$wheelSize = Property::create(['name' => 'Wheel Size', 'type' => 'text']);
echo $wheelSize->name;
// Wheel Size
echo $wheelSize->slug;
// wheel-size
Property Slug
The slug of a property is the URL compatible/friendly variant of the property name, intended to be used in filter URLs, APIs where brevity, human readability and machine compatibility are needed.
Eg.:
- name: Color; slug: color
- name: RAM Size; slug: ram
Example filter URL usage: http://someshop.com/cars?color=red&brand=chrysler&year=2019
The Property and the PropertyValue models use the Eloquent Sluggable package, thus slugs are autogenerated by default from the name field.
If you explicitly set the slug, no auto-generation will take place:
$property = Property::create(['name' => 'RAM Size', 'slug' => 'ram']);
echo $property->slug;
// ram
In case a slug already exists, the slug will be automatically extended to prevent duplicates:
$property1 = Property::create(['name' => 'Property']);
$property2 = Property::create(['name' => 'Property']);
echo $property1->slug;
// property
echo $property2->slug;
// property-1
Retrieving Properties
Find property by id, works the usual way:
$someProperty = Property::find(1);
To find by name:
$screenSizeProperty = Property::findOneByName('Screen Size');
Find by slug:
Property::findBySlug('screen-size');
Property Types
Properties can have types. Built-in types are:
- text
- boolean
- integer
- number
Types are applied to the values of properties.
Adding Custom Types
To add custom types you need to
- Create a class that implements the
PropertyType
interface - Register the type.
Example
namespace App\Models;
class Stars implements \Vanilo\Properties\Contracts\PropertyType
{
public function getName(): string
{
return __('Stars (1-5)');
}
public function transformValue(string $value, ?array $settings)
{
$stars = intval($value);
if ($stars > 5) {
$stars = 5;
} elseif ($stars < 1) {
$stars = 1;
}
return $stars;
}
}
Register the type, preferably in AppServiceProvider::boot()
:
namespace App\Providers;
use App\Models\Stars;
use Illuminate\Support\ServiceProvider;
use Vanilo\Properties\PropertyTypes;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
//...
PropertyTypes::register('stars', Stars::class);
//...
}
//...
}
Creating Property Values
Property values are like "red", "blue" or "white" for the "Color" property; "32GB", "64GB", etc for the "RAM Size" property.
Property values always belong exclusively to one Property. Ie. if you have for example "Drape Color" and "External Color" properties, and both have "black" as value, there will be two separate black value entries.
use Vanilo\Properties\Models\Property;
use Vanilo\Properties\Models\PropertyValue;
$color = Property::create(['name' => 'Color']);
$color->propertyValues()->create([
'title' => 'Red'
]);
// or
$color->propertyValues()->createMany([
['title' => 'Black'],
['title' => 'White'],
['title' => 'Yellow']
]);
// or
PropertyValue::create([
'property_id' => $color->id,
'title' => 'Blue',
'value' => 'blue'
]);
Retrieving Property Values
$colors = Property::find(['name' => 'Color']);
foreach($colors->values() as $color) {
echo $color->title . ', ';
}
// Red, Black, White, Yellow, Blue
Alternatively you can approach from the PropertyValue
model as well:
$property = Property::find(1);
$values = PropertyValue::byProperty($property)->get();
// it also works by passing the property id
$values = PropertyValue::byProperty(1)->get();
Retrieving A Specific Property Value
In case you want to retrieve a specific value from a specific property, you have several ways:
$redColor = Property::findBySlug('color')->propertyValues()->whereSlug('red')->first();
$color = Property::findBySlug('color');
$whiteColor = PropertyValue::byProperty($color)->whereSlug('white')->first();
Since Vanilo v3.1 there's a simplified finder method available:
$red = PropertyValue::findByPropertyAndValue('color', 'red');
Retrieving Multiple Values By Their Scalar Values
This method is available since v4.0
It is possible to query property values using the scalar key/value pairs, where the key is the slug of the property, and the value is the value of the property.
The following example will return a collection, containing two PropertyValue
models, if matching records exist
in the database:
PropertyValue::getByScalarPropertiesAndValues([
'shape' => 'heart',
'material' => 'wood',
]);
// => Collection
Ordering Values
Property values have a priority
field which is being used to sort
values.
To set the priority:
PropertyValue::create(['title' => '16GB', 'priority' => 15]);
PropertyValue::create(['title' => '32GB', 'priority' => 30]);
There are two options to work with the sorted values:
- Use the scopes directly,
- Use the
$property->values()
method, which gives a sorted collection of property values
Sorting Scopes
To retrieve values unsorted, just use the plain propertyValues
relationship:
// Collection of values, without sorting:
$property->propertyValues;
To retrieve values sorted:
// Get the relationship, apply the sort scope, get the results:
$property->propertyValues()->sort()->get();
// The same as above, short form:
$property->values();
// Reverse sort:
$property->propertyValues()->sortReverse()->get();
// Approaching from the PropertyValue model:
PropertyValue::byProperty($propertyId = 1)->sort()->get();
// Same as above, but reverse sorted:
PropertyValue::byProperty($propertyId = 1)->sortReverse()->get();
Using With Products
If you're using the entire framework, then it contains an extended Product Model, that (among others) uses the HasPropertyValues trait.
If you don't use the entire framework but only modules
from it, then you need to extend the product model in your application, eg. declare
a class in app\Models\Product.php
that uses the HasPropertyValues
trait.
Mind that you need to tell the system that now this is your product model via registering it with concord:
app/Providers/AppServiceProvider.php
:
<?php
namespace App\Providers;
use Vanilo\Product\Contracts\Product as ProductContract;
use App\Models\Product;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->get('concord')->registerModel(ProductContract::class, Product::class);
}
}
Using With Any Model
Properties can be added to any Eloquent model in your application. The easiest
way to add properties to an existing model is to add the HasPropertyValues
trait to it:
<?php
namespace App\Models\Vehicle;
use Illuminate\Database\Eloquent\Model
class Vehicle extends Model
{
use HasPropertyValues;
//...
}
Assign Property Values To Models
Once a model has property values, it is possible to assign values to it:
$product = Product::findBySlug('iphone-13-pro-128gb-5g-gold');
$goldColor = PropertyValue::findByPropertyAndValue('color', 'gold');
$product->addPropertyValue($goldColor)
Beginning with Vanilo v3.1, you can also use simplified methods to assign property values, without fetching property records from the database:
// To assign a single property:
$product->assignPropertyValue('color', 'red');
// To assign multiple properties at once:
$product->assignPropertyValues([
'color' => 'red',
'size' => 'XXL',
]);
Obtain The Value Of A Property On A Model
This method was added in Vanilo v3.1:
$product = Product::findBySlug('iphone-13-pro-128gb-5g-gold');
$color = $product->valueOfProperty('color')?->value
// 'gold'
$product->valueOfProperty('color')?->title
// Gold
Removing A Property Value
To remove a property value from a model, use the removePropertyValue()
method:
$event = Event::find(1);
$isOnline = PropertyValue::findByPropertyAndValue('is-online', true);
$event->removePropertyValue($isOnline);
Synchronizing Multiple Values At Once
Under the hood, the $model->propertyValues()
method is a
MorphToMany
relation, therefore multiple values can be
synchronized:
It means, the low level sync() method can be called on it:
$product->propertyValues()->sync([1, 3, 22]);
// 1, 3 and 22 are the ids of property values
Since Vanilo v4.0, it is also possible to sync the assigned property values using scalar key/value pairs:
// Assigns two property values:
$product->replacePropertyValuesByScalar(['finish' => 'glossy', 'diameter' => 16]);
$product->valueOfProperty('finish')->value;
// => (string) "glossy"
$product->valueOfProperty('diameter')->value;
// => (int) 16
// Replaces finish from glossy to matte, and removes the diameter property value
$product->replacePropertyValuesByScalar(['finish' => 'matte']);
$product->valueOfProperty('finish')->value;
// => (string) "matte"
$product->valueOfProperty('diameter');
// => NULL
If the passed value doesn't exist, it will be created:
$color = Property::create(['name' => 'Color']);
$color->propertyValues;
// => Empty collection
$product->replacePropertyValuesByScalar(['color' => 'magenta']);
$color->propertyValues;
// => Illuminate\Database\Eloquent\Collection {#24002
// all: [
// Vanilo\Properties\Models\PropertyValue {#24001
// ...
// value: "magenta",
// ...