Associations define relationships between products, such as cross-sells, up-sells, and alternates.
Overview
Associations allow products to be related to each other. The type of association defines how the relationship should be presented on a storefront and how Lunar interprets it. Lunar ships with three built-in types (cross-sell, up-sell, and alternate), but custom types can also be created.
Model
Associations are stored as Lunar\Models\ProductAssociation models.
Fields
| Field | Type | Description |
|---|
id | bigIncrements | Primary key |
product_parent_id | foreignId | The owning product |
product_target_id | foreignId | The associated product |
type | string | The association type (e.g. cross-sell, up-sell, alternate) |
created_at | timestamp | |
updated_at | timestamp | |
Relationships
| Relationship | Type | Related Model | Description |
|---|
parent | BelongsTo | Lunar\Models\Product | The owning product |
target | BelongsTo | Lunar\Models\Product | The associated product |
Scopes
| Scope | Description |
|---|
crossSell() | Filter to cross-sell associations |
upSell() | Filter to up-sell associations |
alternate() | Filter to alternate associations |
type(ProvidesProductAssociationType|string $type) | Filter by a specific type |
Association Types Enum
The built-in association types are defined using the Lunar\Base\Enums\ProductAssociation enum:
use Lunar\Base\Enums\ProductAssociation;
ProductAssociation::CROSS_SELL; // 'cross-sell'
ProductAssociation::UP_SELL; // 'up-sell'
ProductAssociation::ALTERNATE; // 'alternate'
The Lunar\Models\ProductAssociation model also defines CROSS_SELL, UP_SELL, and ALTERNATE as class constants, but these are deprecated since v1.2.0. Use the Lunar\Base\Enums\ProductAssociation enum instead.
Loading Associations
This returns a collection of Lunar\Models\ProductAssociation models:
use Lunar\Models\ProductAssociation;
$association->parent; // The owning product
$association->target; // The associated product
$association->type; // The association type string
Inverse Associations
To find products that associate to a given product (i.e. where the product is the target), use the inverseAssociations relationship:
$product->inverseAssociations;
Types of Association
Cross-Sell
Cross-selling encourages customers to purchase complementary products in addition to the item they intended to buy.
For example, if a store sells a phone, cross-sell associations could include headphones or a case that works with that phone.
Adding a cross-sell association
use Lunar\Base\Enums\ProductAssociation;
$product->associate($crossSellProduct, ProductAssociation::CROSS_SELL);
// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::CROSS_SELL);
Fetching cross-sell associations
use Lunar\Base\Enums\ProductAssociation;
// Using the convenience scope
$product->associations()->crossSell()->get();
// Using the type scope
$product->associations()->type(ProductAssociation::CROSS_SELL)->get();
Up-Sell
Up-selling encourages customers to upgrade or include add-ons to the product they are buying, typically to a higher-value option.
For example, given two phones:
- Phone 16GB 5” Screen
- Phone 32GB 6” Screen
The 32GB version could be added as an up-sell association on the 16GB product, allowing it to be presented as an upgrade when a customer views the 16GB version.
Adding an up-sell association
use Lunar\Base\Enums\ProductAssociation;
$product->associate($upSellProduct, ProductAssociation::UP_SELL);
// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::UP_SELL);
Fetching up-sell associations
use Lunar\Base\Enums\ProductAssociation;
// Using the convenience scope
$product->associations()->upSell()->get();
// Using the type scope
$product->associations()->type(ProductAssociation::UP_SELL)->get();
Alternate
Alternate products are alternatives to the current product. This is useful when a product is out of stock or not quite the right fit, allowing the storefront to suggest similar options.
Adding an alternate association
use Lunar\Base\Enums\ProductAssociation;
$product->associate($alternateProduct, ProductAssociation::ALTERNATE);
// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::ALTERNATE);
Fetching alternate associations
use Lunar\Base\Enums\ProductAssociation;
// Using the convenience scope
$product->associations()->alternate()->get();
// Using the type scope
$product->associations()->type(ProductAssociation::ALTERNATE)->get();
Custom Types
In addition to the built-in types, custom association types can be defined by passing any string as the type. No registration or configuration is required since the type column is a plain string in the database.
$product->associate($relatedProduct, 'my-custom-type');
Custom types can be queried using the type scope:
$product->associations()->type('my-custom-type')->get();
If using the admin panel, custom types can be added to the association type dropdown by replacing the default enum. See Admin Panel Configuration for details.
Removing Associations
The dissociate method removes associations between products. It accepts a single product, an array, or a collection. If no type is specified, all association types for the given product(s) are removed.
use Lunar\Base\Enums\ProductAssociation;
// Remove all associations with a product, regardless of type
$product->dissociate($associatedProduct);
// Also accepts an array or collection of products
$product->dissociate([$productA, $productB]);
// Remove only a specific association type
$product->dissociate($associatedProduct, ProductAssociation::CROSS_SELL);
Queued Operations
Both associate() and dissociate() dispatch queued jobs (Lunar\Jobs\Products\Associations\Associate and Lunar\Jobs\Products\Associations\Dissociate). This means the changes may not be reflected immediately if using an asynchronous queue driver.