Problem/Motivation
We did two tasks for creating and improving to generate bundles from combinations of products.
Still we lack some flexibility which would be per project required - so we essentially need event subscriber where we would be able to alter number of combinations, fill maybe some data during creation, etc..
Steps to reproduce
Proposed resolution
Add event subscriber to react prior bundle variations are created - to unset some combinations, to fill some data
Move perhaps some of the logic in dedicated service so that would allow more re-usability of this logic, outside of current form and people can build their own UI interface and use that service
Remaining tasks
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | Screenshot From 2026-08-26 16-10-11.png | 142.71 KB | valic |
Issue fork commerce_variation_bundle-3619230
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
valicUpdated UI
Comment #4
valicFollow-up on the bulk generation form. Three things this adds: the generation
becomes callable without the form, subscribers get to decide which combinations
are worth creating, and the generated titles become configurable.
1. Generation extracted into a service
BundleVariationGenerator, its interface and a smallBundleVariationGenerationResult, registered ascommerce_variation_bundle.variation_generatorwith an interfacealias. Nothing in it touches form state, so a drush command, a migration, a
queue worker or a different UI can generate bundles without going through
GenerateBundleVariationsForm.$optionsacceptsvariation_type,template,fields,skuandtitle. The variation type is derived from the product when it isleft out. SKU building gained
prefix,separatorandsource(skuorid) on top of what theform had.
The form is 127 lines shorter and delegates to the service.
buildSku()andcartesianProduct()moved into it.2. Two events
GENERATE_VARIATIONSGENERATE_VARIATIONskip()Which combinations are worth selling is a question about the catalogue, and
the module cannot answer it. Combining four products of 4, 11, 10 and 3
variations offers 1320 combinations; on the site this was written for, roughly
240 of those are real products and the rest pair sizes, pack counts or market
variants that do not go together. Removing them here is much cheaper than
creating variations and deleting them afterwards.
Two details worth calling out:
skip()leaves no orphanedcommerce_bundle_itementities behind.
setCombinations()reindexes its argument, so a subscriber canuse
array_filter()without renumbering the result.getCombinations()is the method that dispatches, and the form'scombination count goes through it. Without that the form previewed and
limit-checked the raw cartesian product: on a real selection it reported 960
combinations and refused the run against the 500 limit, while only 288 would
have been created. A UI counting combinations should use
getCombinations();buildCombinations()is the plainarithmetic. Subscribers therefore run on every preview and are expected to be
cheap and free of side effects.
3. Configurable generated titles
Both title formats were hardcoded —
sprintf('%sx %s', ...)inBundleItem::generateTitle()and
implode(' / ', ...)inVariationBundle::generateTitle(). They are now configuration, onthe config entity that owns the title each one formats:
titlePatterncommerce_bundle_item_type, as a property@quantityx @titletitle_separatorcommerce_product_variation_type, as a third-party setting&Exposed as
BundleItemTypeInterface::DEFAULT_TITLE_PATTERNandVariationBundleInterface::DEFAULT_TITLE_SEPARATOR.A bundle item cannot look up the variation type that holds it — there
is no back-reference — so the item pattern has to live on the item type
rather than alongside the separator. That is what forces the split; it is not a
preference.
The generate form can override both for a single run, but only when the
variation type does not generate titles for itself. A type that does has its
title recomputed from the type on every save, so an override could not survive;
that case shows the resulting format read-only and links to the variation type
instead.
4. Form changes
target_bundles.#validate_referenceis set toFALSEalongside it, so a product typed in full is refused byaddProduct()with a reason — it is a bundle, or it is thisvery product — rather than by core with "The referenced entity does not
exist", which is both untrue and unhelpful.
#requiredcleared. Core validates required elements before any#validatehandler runs, so hiding it with#statesalone left the form unsubmittable.
5. Update hook
commerce_variation_bundle_update_10001()— the module'sfirst, the installed schema was 8000. It adds the default
titlePatternto bundle item types created before the propertyexisted, skipping any that already carry one, so it is safe to re-run.
Comment #5
valic