Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Contracts Laravel Package

vanilo/contracts

vanilo/contracts defines the shared PHP interfaces behind Vanilo’s e-commerce components, providing consistent contracts for products, orders, customers, pricing, and related services so Laravel packages can interoperate cleanly.

View on GitHub
Deep Wiki
Context7

Getting Started

  • You usually don't install this directly. vanilo/contracts is pulled in transitively by vanilo/framework, vanilo/product, vanilo/order, vanilo/cart, etc. Run composer why vanilo/contracts to see who depends on it and which version constraint is in play before you touch anything.
  • Add it explicitly (composer require vanilo/contracts) only when you are building your own integration package — a payment gateway, a warehouse sync, a product importer — that must talk to Vanilo without dragging in the whole framework, or when you want to unit-test against the domain interfaces without a database.
  • Look here first: vendor/vanilo/contracts/src/. It is small — one interface per file — and reading the whole directory once gives you Vanilo's domain map: Product, Buyable, Priceable, Taxon, Cart, Checkout, Order, Customer, Address, Billpayer, PaymentRequest/PaymentResponse, shipping/shippable contracts.
  • First use case: stop type-hinting concrete Eloquent models in your own services. Swap App\Models\Product for Vanilo\Contracts\Product in a constructor, and the same service now works with stock models, your custom models, and test doubles:
namespace App\Services;

use Vanilo\Contracts\Product;

class PricingService
{
    public function __construct(private Product $product) {}

    public function effectivePrice(): float
    {
        return $this->product->getPrice() * (1 - $this->discount());
    }
}

If that resolves from the container without a manual binding, the package's service provider already maps the interface; if it throws, see the gotchas below.

Implementation Patterns

Interface-typed services (the 80% case). Inject contracts at the edges of your code and keep concrete models at the persistence boundary. Business logic (pricing, eligibility, checkout orchestration) depends only on the contract; Eloquent stays in repositories and controllers.

Custom models that implement the contracts. When you replace a Vanilo model with your own (a Course or Subscription that is a "product"), implement the relevant interfaces on your model and point the package's model mapping at it — the models entry in the package config or the model registration in your module service provider. The contract is the checklist: every accessor listed must exist and return the declared type.

Adapters for legacy domains. The highest-value pattern when migrating onto Vanilo. Wrap an existing table/model and delegate:

class LegacyProductAdapter implements \Vanilo\Contracts\Product
{
    public function __construct(private \App\Legacy\Item $item) {}

    public function getName(): string { return $this->item->title; }
    public function getPrice(): float { return (float) $this->item->price_cents / 100; }
    // ...remaining contract methods delegate to $this->item
}

Now your new checkout code consumes legacy data without a big-bang migration.

Plugging in a payment provider. Implement the payment request/response contracts (plus the gateway contract your Vanilo version exposes) and register the gateway. Because these are value-like objects, make them serializable if you persist or queue them.

Custom shipping / carriers. Same shape: implement the shipping-method contract, return a cost for a given shippable/address, and register it with the shipping subsystem.

Sub-interfacing for your own domain. When you need methods the core doesn't declare, define interface SellableProduct extends \Vanilo\Contracts\Product { public function subscriptionLength(): int; } and implement both. Extend, don't fork — core packages keep accepting the parent type.

Testing. The contracts are plain interfaces, so they are trivial to mock with Mockery or anonymous classes. This is the cheapest way to unit-test a checkout flow: fake Cart, Billpayer, and PaymentResponse, assert your orchestration, no database.

Static analysis. Add PHPStan/Larastan and type-hint contracts in your service layer. The analyzer will then catch places where you leaked an Eloquent-only method (->where(...), ->save()) into code that is supposed to be implementation-agnostic.

Gotchas and Tips

  • Zero behavior lives here. No base classes, no traits, no defaults. You cannot new anything from this package; it is a vocabulary, not an implementation. If you expected helper methods, they are in the concrete packages.
  • Version skew is the #1 failure. Contracts move with Vanilo majors. Running vanilo/contracts v3 against vanilo/order v2 produces fatal signature mismatches that look like unrelated bugs. Pin the whole Vanilo stack together and check the changelog on upgrade — even a minor addition to an interface is breaking for anyone who implements it.
  • Target [Vanilo\Contracts\X] is not instantiable means the interface was never bound to a concrete class. Before assuming a container bug, check whether the package resolves these from a model map rather than a binding — app(SomeContract::class) is not guaranteed to work for every contract. Resolve the concrete model and trust instanceof instead of injecting the interface blindly.
  • Return types are enforced, DB columns are not. MySQL happily hands you "19.99" strings. A contract declaring float will throw a TypeError at runtime. Add $casts on the implementing model and test with real (non-sqlite) data at least once.
  • The contract fixes method names, not columns. Vanilo's stock models assume specific column names and relations. A custom implementation must satisfy the interface and whatever the consuming package queries. Read the consumer's repository code, not just the interface.
  • Watch nullability. Billpayer, shipping address, and price can be nullable by design (e.g. a product not yet priced). Handle null explicitly; don't let a ?Address reach a method expecting Address.
  • Don't type-hint both. A parameter typed as Model&\Vanilo\Contracts\Product is usually a sign the abstraction isn't buying you anything. Pick the boundary: contracts for logic, models for persistence.
  • Lazy loading hides behind accessors. A contract method like getItems() may trigger a relation load. Calling it in a loop over 500 orders is an N+1. Eager-load in the repository before handing objects across the contract boundary.
  • instanceof guards get noisy. Scattering if ($x instanceof Product) through your code defeats the point. Prefer a single adapter at the edge over defensive checks everywhere.
  • The 3-star count is misleading. This is internal plumbing consumed transitively; nobody stars their illuminate/contracts either. The real signal is the consuming packages — read their composer.json for the supported contracts range. The opportunity here is contribution: adding a well-justified method to an interface (with adapters in the core packages) is a small change with wide reach.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core