Problem/Motivation

When a product with a new variation is created through Inline Entity Form (complex widget) inside a parent form (e.g. a node), and the editor re-opens that product's inline form and clicks "Update" before saving the parent, saving the parent throws:
Drupal\Core\Entity\EntityStorageException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '<uuid>' for key 'commerce_product_variation_field__uuid__value': INSERT INTO "commerce_product_variation" ("type", "uuid", "langcode") ...

The variation is inserted once (orphaned, product_id = NULL), the product and the parent are not saved, and the parent stays locked by content_lock if enabled.

Steps to reproduce

  1. Node type with an entity reference field to commerce_product, widget "Inline entity form – Complex", allow new. Product type with the default variations IEF complex widget.
  2. Edit/create a node → "Add new product" → "Add new variation" → fill SKU/price → "Create variation" → "Create product". (Do not save the node yet.)
  3. Click "Edit" on the product row → click "Update product" (no changes are necessary).
  4. Save the node → 500 with the error above.

Not affected: saving right after step 2; the standalone product add/edit form; products that already exist in the database.

Root cause

  1. During step 2, validation of the inline product form instantiates the computed default_variation field (ComputedDefaultVariation::computeValue()), which calls Product::getDefaultVariation() and caches the (still unsaved) variation object in Product::$defaultVariation (added in #3135918).
  2. In step 3 the inline form's #entity comes from the $form cache, which is serialized separately from $form_state. That copy carries its own unserialized copy of the unsaved variation in $defaultVariation and in the computed field's values. IEF's entityFormSubmit() rebuilds variations from the widget state (form_state copy), but $defaultVariation is never reset, so the product now references two distinct PHP objects with the same variation UUID.
  3. On save, IEF's WidgetSubmit::doSubmit() saves the widget-state variation first. Then the product is saved, and ContentEntityStorageBase::invokeFieldMethod('preSave') iterates all fields including computed ones (getFields() defaults to $include_computed = TRUE). EntityReferenceItem::preSave() on the default_variation item sees hasNewEntity() and saves the stale copy → second INSERT with the same UUID.

Verified by loading the cached form state in a drush script: $product->get('variations')[0]->entity is the same object as the IEF widget-state variation, while Product::$defaultVariation (and values['default_variation']) is a different object with the same UUID and isNew() === TRUE. Running WidgetSubmit::doSubmit() on that state reproduces the 1062 outside of any HTTP request.

Proposed resolution

A computed field must never persist anything. Override preSave() in ComputedDefaultVariation as a no-op:

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    // This list is computed. Core invokes preSave() on computed fields too
    // (ContentEntityStorageBase::invokeFieldMethod() uses getFields() with
    // $include_computed = TRUE), and EntityReferenceItem::preSave() would save
    // a stale, unserialized copy of an unsaved variation a second time.
  }

Optionally also drop $defaultVariation from serialization (__sleep()), so a stale copy can never survive a form-cache round-trip.

Environment: Drupal 11.4.7, Commerce 3.3.9, Inline Entity Form 3.0.0, PHP 8.3.33. Same code in 3.x-dev.

Issue fork commerce-3624795

Command icon 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

tky created an issue. See original summary.

hai nguyen made their first commit to this issue’s fork.

hai nguyen’s picture

Status: Active » Needs review