vanilo/support
Vanilo Support provides the shared foundation for the Vanilo e-commerce suite: common contracts, helpers, traits, and base classes used across its components. A lightweight utility package for Laravel developers building modular online stores.
vanilo/support is a low-level utility package and is almost never installed directly — it arrives transitively via vanilo/concord, vanilo/framework, or any concrete Vanilo module (products, cart, checkout, etc.). Install it explicitly only if you're building a standalone model or package that needs the shared contracts, traits, and helpers without pulling the full e-commerce stack:
composer require vanilo/support
Where to look first, in this order:
src/Contracts/ — the interfaces your Eloquent models should implement (Sellable, Buyable, Priceable, Taxable, Shippable, Shoppable, etc.). These drive polymorphism across the whole Vanilo stack, so they are the most important surface.src/Traits/ — the batteries-included implementations: HasPrices, HasDimensions, HasName, HasSlug, HasMedia, HasImages, LoadsRelationships, Features.src/Enums/ — DimensionUnit, WeightUnit, and friends, used as typed enums for unit-safe comparisons.src/Features/ — the feature-flag machinery used to selectively enable/disable Vanilo subsystems.First use case: you have an Eloquent model (say a SubscriptionPlan or a GiftCard) that needs to behave like something sellable inside Vanilo. Implement Sellable and use HasPrices, and your model can be put in a cart, have taxes computed, and be priced by the rest of the framework without custom glue.
Contract-first models. The idiomatic pattern is: implement a Vanilo contract, pull in the matching trait. Don't implement the contract from scratch — the traits already handle the attributes, casts, and Money conversion in a way the other modules expect.
use Illuminate\Database\Eloquent\Model;
use Vanilo\Support\Contracts\Sellable;
use Vanilo\Support\Traits\HasPrices;
class GiftCard extends Model implements Sellable
{
use HasPrices;
}
Extend, don't reinvent. Most day-to-day work happens by extending Vanilo's own models (Vanilo\Product\Models\Product, Vanilo\Cart\Models\Cart, etc.). Those already use the traits from this package, so inherited price accessors, dimensions, and slug handling all come for free. Your job is usually migrations and form validation, not reimplementing accessors.
Compose traits, don't stack them blindly. HasPrices + HasDimensions + HasSlug on the same model is common; each trait is scoped to a small set of columns, so there is little overlap. If you add your own price accessor and HasPrices, you will hit a trait-method collision — resolve with insteadof / as.
Feature flags for conditional behaviour. Wrap optional integrations (wishlist, reviews, payment gateways) with the Features trait + Featureable interface. Gate code paths with $model->isFeatureEnabled('...') rather than raw config() lookups, so toggling features config flips the whole stack consistently.
Money is typed, never float. HasPrices casts to a Money value object (through Concord's money resolver). Always operate on the accessor (e.g. $model->price) and use the value-object API; never read attributes['price'] directly, and never + two prices as numbers.
Unit-safe dimensions. HasDimensions pairs with DimensionUnit / WeightUnit enums. Convert units through the enum before arithmetic or persistance, so you don't silently mix cm and in in shipping calculations.
Vanilo\Contracts\* → Vanilo\Support\Contracts\*). Pin vanilo/support to the same release line as the rest of your Vanilo packages and bump them together.HasPrices expects specific column names — normally price, original_price, sale_price, all integer minor-units columns. If your migration uses cost or retail_price, the trait will return nulls rather than throwing, which is a silent bug. Mirror the naming.getPriceAttribute() yourself while HasPrices is in place, PHP trait precedence rules apply — you must use insteadof/as, or your accessor may be replaced. Check class_uses(YourModel::class) when in doubt.Concord::money() returns null (missing/config typo in config/concord.php or config/vanilo.php), the price cast fails on hydrate, not on write — errors surface far from the cause. Verify the money driver is registered in a service provider during boot.Features needs the Featureable interface. Using the trait alone compiles but the feature resolution methods type-hint the interface at some call sites; forgetting it throws a confusing TypeError at runtime, not at boot.vanilo.features.*, not concord.features.* and not a per-package key. A common debugging trap is adding flags under the wrong root and wondering why nothing toggles.LoadsRelationships reads a $model->load property (or config) and eagerly loads from it. If you add relations dynamically in code, remember to update that list too, or the trait won't see them.Sellable/Priceable contract in a Concord module service provider (Concord::register(...)). This is the supported way to customise behaviour; extending the traits directly is supported but fragile across versions.$model->getCasts() — the trait adds casts in its initialize* hook, so if your model's $casts overrides them by name, the trait's cast silently loses.@internal) helpers. They exist to support the surrounding modules and are the first thing to disappear between minor releases; stick to the documented contracts, traits, and enums.How can I help you explore Laravel packages today?