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

Variation Laravel Package

sylius/variation

Sylius Variation is a PHP component that provides reusable domain building blocks for modeling product variations in Sylius-based e-commerce applications, helping organize variation values and related catalog configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand what you're adopting first. sylius/variation is an archived, read-only extraction of the legacy Sylius Variation component (the pre-ProductVariant era concept). It has essentially no dependents and is not a good choice for greenfield work — it exists to keep older Sylius installs and downstream forks installable, and as a small, readable reference implementation of "generate all variations of an object from a set of options."

Install and inspect. Because it's archived, Composer will resolve it but you get no security or bug fixes. Prefer vendoring/forking it if you actually depend on it:

composer require sylius/variation

Read in this order:

  1. src/Model/VariationInterface.php — the contract for a variation (holds a collection of options; addOption, hasOption, removeOption, getOptions).
  2. src/SetBuilder/RecursiveSetBuilder.php (+ SetBuilderInterface, SetInterface) — builds the cartesian product of option groups into a list of option "sets".
  3. src/Generator/GeneratorInterface.php and VariationGenerator — applies a SetInterface onto a VariationInterface.
  4. src/Model/Variation.php — the reference model you'll extend or replace.

First use case — expand option groups into variations. The canonical flow is: build sets from a nested options array, then run a generator over each set to populate variations:

use Sylius\Component\Variation\SetBuilder\RecursiveSetBuilder;
use Sylius\Component\Variation\Generator\VariationGenerator;
use Sylius\Component\Variation\Model\Variation;

$setBuilder = new RecursiveSetBuilder();

// color x size -> six combinations
$sets = $setBuilder->build([
    'color' => ['red', 'blue'],
    'size'  => ['S', 'M', 'L'],
]);

$generator = new VariationGenerator();

$variations = [];
foreach ($sets as $set) {
    $variation = new Variation();
    $generator->generate($set, $variation); // mutates in place
    $variations[] = $variation;
}

If that shape matches your problem (product matrices, subscription tiers, any cartesian expansion of attributes), the package is a compact reference. If not, the dependency is likely not pulling its weight — the codebase is small enough that reimplementing the builder/generator in your own namespace is often cheaper than maintaining an archived dependency.

Implementation Patterns

SetBuilder → Generator is the core pipeline. SetBuilderInterface::build(array $options) returns SetInterface[]; each SetInterface is one point in the cartesian space (an associative array of option group => value). GeneratorInterface::generate(SetInterface $set, VariationInterface $variation) writes the options from the set onto the variation. Keep the builder and generator decoupled in your services — you will swap one or both.

Use the generator as your extension point for business rules. VariationGenerator itself does no validation. The idiomatic pattern is to decorate it, or implement GeneratorInterface yourself, to skip impossible combinations (e.g., a "one-size" product that must not get an S/M/L expansion, or a color-only matrix when size is absent):

final class RestrictedVariationGenerator implements GeneratorInterface
{
    public function __construct(private GeneratorInterface $inner) {}

    public function generate(SetInterface $set, VariationInterface $variation): void
    {
        if ($this->isImpossible($set)) {
            throw new \DomainException('Invalid option combination');
        }

        $this->inner->generate($set, $variation);
    }
}

Persist via Doctrine by extending the model, not the interface. In practice you'll create your own Variation entity extending Sylius\Component\Variation\Model\Variation (or just implementing VariationInterface), map the options association to your option entity, and register the package's builder/generator as plain services — the component ships no DI configuration, so wire it explicitly:

services:
    app.variation.set_builder: { class: Sylius\Component\Variation\SetBuilder\RecursiveSetBuilder }
    app.variation.generator:
        class: Sylius\Component\Variation\Generator\VariationGenerator

Bulk generation workflow. For admin "generate variants" buttons: normalize submitted option groups → setBuilder->build() → filter/reject via your generator decorator → persist each variation → flush once. Batch at the collection level so you can report "3 of 6 combinations were invalid" back to the user rather than throwing mid-loop.

Testing pattern. Because the interfaces are tiny, unit-test your generator against a fake SetInterface returning a known options array, and assert on $variation->getOptions(). This is faster and more stable than booting Doctrine, and it's the primary way you'll guard your restriction logic.

Treat it as a template, not a framework. The package is small: a model contract, a recursive builder, and a generator. Copy the pattern into your own App\Catalog\Variation namespace if you need it long-term, and keep sylius/variation only as the thing you're migrating away from.

Gotchas and Tips

Archived status is the headline gotcha. No security patches, no PHP 8.x/9.x modernization guarantees, and issues/PRs are closed. Pin an exact version in composer.json, and if you ship it to production, vendor or fork it so a future Composer resolution failure can't take your build down.

Combinatorial blowup. RecursiveSetBuilder produces the full cartesian product — option groups multiply. Five groups of five values is 3,125 sets before any filtering. Guard at the boundary: cap total combinations, reject absurd inputs, and consider a streaming/lazy builder if the count can be large. The generator itself doesn't dedupe; identical option values across groups will happily produce duplicate-looking variations.

The generator mutates; it doesn't create. generate() takes an existing VariationInterface and populates it. If you expect it to return a new variation you'll get null and a confusing result. Create the variation yourself, then pass it in.

No restriction/exclusion logic is built in. This surprises people: the package gives you the cartesian product, not "exclude size S in red." Any business filtering lives in your decorator or a post-step that inspects getOptions(). Add it deliberately and test it — it's the part most likely to differ from expectations.

Duplicate option handling. hasOption/addOption on the model are collection-based; depending on your model implementation, adding the same option twice may silently duplicate or be ignored. Normalize option identity (by code/name) before calling addOption, and assert uniqueness in your own code rather than relying on the component.

Namespace and era mismatch. This is the legacy Sylius\Component\Variation component, distinct from ProductVariant/ProductOption concepts in modern Sylius. Don't follow current Sylius docs and expect them to map — read the package source, not the current manual. If you're on modern Sylius, you almost certainly want the product/variant components instead.

No container glue means no conventions. There's no tagged service, no factory, no config tree. You own instantiation and lifecycle. That's freeing but means "it doesn't work out of the box" is expected — verify autoloading and constructor arguments before assuming a bug.

PHP version drift. Expect untyped or loosely typed signatures relative to your codebase's standards; wrap the component behind your own typed interfaces so a future replacement or patch fork doesn't ripple through the whole app.

Debugging tips. Dump each SetInterface (it's array-like via getOptions()) before calling the generator; if variations come out empty, the problem is almost always an empty/incorrectly structured options array passed to build(), or a decorator short-circuiting generate(). When in doubt, run the builder alone with a two-by-two input and confirm you get four sets.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource