Vanilo 6.0 Released.See what's new
On this page
Components Product Properties
7 min read

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:

  1. Use the scopes directly,
  2. 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",
//       ...
Next in Components Product Variants