Product Properties

The Properties Module first appeared in Vanilo v0.5.

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". Spree Commerce for example also has this naming: 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;

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\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();

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();