Commerce 2.x Stories: Products
Previously we talked about currencies and stores. This week we’ll focus on products.
Before describing the improvements we’ve made, let’s remember the previous implementation.
Commerce 1.x has a flexible but slightly unfriendly product architecture.
Products represent individual purchasable SKUs: a small red t-shirt, a file, a conference ticket. Products can have attributes, which are special fields such as a Size or Color field used to switch between grouped products on the Add to Cart form. Products are grouped together in product display nodes. A product display node is a node that has a product reference field, allowing a “DrupalCon T-Shirt” product display to reference the individual SKUs (combinations of color and size).
Products need to be created individually in their own UI, then referenced from a product display node. This means having to use two different UIs to do what is essentially one operation. Back in 2012 we developed Inline Entity Form, allowing products to be managed directly on the product display form, and used it as a basis of the then new Commerce Kickstart 2.x distribution. It has since become a standard for managing products in Commerce.
Kickstart also tried to relabel the relevant entity types, calling the commerce_product entity type “product variation“ and calling product display nodes “products”. This rename was only skin deep (limited to the UI), and created confusion for documentation writers and readers.
Looking back, what caused the most trouble?
- Inline Entity Form
Not having / requiring Inline Entity Form from the start.
- Mixing product and non-product content
This made it necessary for Kickstart to create separate views for product and non-product content. The same was done for content types.
- Different ways of creating attribute fields
Attribute fields can be any field with an options list, such as taxonomy_term_reference fields (pointing to a vocabulary of terms) or list_string fields. This increases the documentation burden, and creates the question of “which approach to use“. Kickstart standardized on taxonomy_term_reference fields, but at that point it was already obvious that too much choice is not always a good thing.
- Data model paper cuts
Product displays weren’t required (even though most people used them), so there was no canonical way of getting the product’s display. Modules had to reinvent this relationship using EntityFieldQuery. Deleting a product display node didn’t delete the referenced products, since there was no guarantee they weren’t going to be referenced elsewhere. Once again this had to be done in Inline Entity Form.
Products had required titles, even though attributes were usually displayed instead. Inline Entity Form thus had to offer an option to auto-generate the title, which was sometimes buggy.
In the new architecture, a Product entity references one or more ProductVariation entities. Thus the product entities replace D7 product display nodes (and match the D8 nodes visually) while the variation entities replace D7 product entities. Each product type has a matching product variation type. A product always references variations of the same type.
See also the full product edit form.
Variations are only manageable from the parent product, using Inline Entity Form, which is now a Commerce dependency. Variation titles are also no longer stored. They are dynamically constructed from the attribute labels instead, so there’s no more need for auto generation on insert. Deleting a product deletes its variations. Adding a variation to a product automatically creates a backreference on the variation, accessed via $variation->getProduct().
Attributes are now entity reference fields. Referencing entities such as terms allows the usage of fields to hold things such as the image representation of the attribute, the color value for a color swatch, etc.
But what happens if a variation type has no attributes? For example, a product is only selling a single file. In that case, the Inline Entity Form widget will render the variation form as a regular fieldset on the product. This piece is still in progress but will be ready for the alpha release.
Custom product architectures
When it comes to product architectures, there is no one true answer. Furthermore, different clients might have different needs. That’s why it’s important for Commerce 2.x to support any number of product architectures.
The ProductVariation entity class implements the PurchasableEntityInterface:
Any content entity type that implements this interface can be purchased. The order module doesn’t depend on the product module, the product module just provides the default (and most common) product architecture. A product bundle module will probably want to define its own product architecture, etc.
Line items have a purchased_entity reference field. The target_type of that reference field is different for each line item type.
Here the line item type points to the product variation entity type, indicating that the "Product variation" line item type is used to purchase product variations.
Early in the Commerce 2.x cycle we explored the idea of hierarchical products, but after initial exploration found out that the idea required several months of extra effort (having to rewrite the Tree module, reinvent an IEF like widget, UX and performance considerations). We removed it from the roadmap with a heavy heart, but now that Commerce 2.x supports custom product architectures, we can easily explore the idea in contrib at a later date.
Commerce 2.x ships with much better UX out of the box, thanks to a revamped product architecture. It also features under the hood improvements that allow developers to implement custom product architectures for different use cases.