Best way to influence the values returned on an entity's field relationship route

Created on 5 March 2019, about 6 years ago
Updated 25 January 2025, 2 months ago

For Commerce, I provided an override to the `getRelated` route for `commerce_product--{type}.variations.related`. This allows us to follow internal API logic for filtering valid variations. I have added here: https://github.com/mglaman/commerce_demo_jsonapi/blob/master/src/Control....

This does not filter values from the actual variations reference field, that's a "to figure out." Ideally, I could filter out what is returned by referencedEntities on the field level. But, due to earlier limitations in Drupal core variations is not a base field (yet) and you can't provide a custom field list class per field definition -- only field type.

For instance, a product could have twenty variations. The store has logic which filters out some variations based on the user's country, selected store, or preferred currency. This is why we have the ProductEvents::FILTER_VARIATIONS event, which is fired from the variation storage loadEnabled method. This is the method used to determine valid variations for the add to cart form.

I don't see any way to filter out access for the entities returned on a relationship route unless all logic lived in `view` which still returned `Access denied` entries in the list (bad DX in my opinion.) I would rather see the unavailable product variations removed rather than the response contain "sorry, this isn't accessible."

----

Here is the normal code for getRelated:

    // Remove the entities pointing to a resource that may be disabled. Even
    // though the normalizer skips disabled references, we can avoid unnecessary
    // work by checking here too.
    /* @var \Drupal\Core\Entity\EntityInterface[] $referenced_entities */
    $referenced_entities = array_filter(
      $field_list->referencedEntities(),
      function (EntityInterface $entity) {
        return (bool) $this->resourceTypeRepository->get(
          $entity->getEntityTypeId(),
          $entity->bundle()
        );
      }
    );
    $collection_data = [];
    foreach ($referenced_entities as $referenced_entity) {
      $collection_data[] = $this->entityAccessChecker->getAccessCheckedResourceObject($referenced_entity);
    }

In my override,

    $referenced_entities = array_filter($field_list->referencedEntities(),
      function (ProductVariationInterface $entity) {
        return (bool) $this->resourceTypeRepository->get($entity->getEntityTypeId(), $entity->bundle());
      }
    );

     # START COMMERCE LOGIC from variation storage.

    // Remove entities which cannot be viewed.
    $referenced_entities = array_filter($referenced_entities,
      function (ProductVariationInterface $variation) {
      return $variation->access('view');
    });
    // Allow modules to apply own filtering (based on date, stock, etc).
    $event = new FilterVariationsEvent($entity, $referenced_entities);
    $this->eventDispatcher->dispatch(ProductEvents::FILTER_VARIATIONS, $event);
    $referenced_entities = $event->getVariations();

     # END COMMERCE LOGIC

    $collection_data = array_map([$this->entityAccessChecker, 'getAccessCheckedResourceObject'], $referenced_entities);

---

I am not sure of any way to easily alter the variations returned from this route. Or the field in general. Ideally, this would only run during JSON:API calls, so we can preserve any BC for working in normal PHP code.

---

Adding examples of the loadEnabled usage.

Cart Flyout: fetching variations to serialize and embed as JSON on the page

http://cgit.drupalcode.org/commerce_cart_flyout/tree/src/Plugin/Field/Fi...

  protected function loadVariations(ProductInterface $product) {
    return array_reduce(
      $this->variationStorage->loadEnabled($product),
      function ($carry, ProductVariationInterface $variation) {
        $carry[$variation->uuid()] = $variation;
        return $carry;
      }, []);
  }

Add to cart: attributes widget

http://cgit.drupalcode.org/commerce/tree/modules/product/src/Plugin/Fiel...

  protected function loadEnabledVariations(ProductInterface $product) {
    $langcode = $product->language()->getId();
    $variations = $this->variationStorage->loadEnabled($product);
    foreach ($variations as $key => $variation) {
      $variations[$key] = $this->entityRepository->getTranslationFromContext($variation, $langcode);
    }
    return $variations;
  }

Best example is the TitleWidget, which takes the variations and renders a select list.

http://cgit.drupalcode.org/commerce/tree/modules/product/src/Plugin/Fiel...

    $variations = $this->loadEnabledVariations($product);
    if (count($variations) === 0) {
      // Nothing to purchase, tell the parent form to hide itself.
      $form_state->set('hide_form', TRUE);
      $element['variation'] = [
        '#type' => 'value',
        '#value' => 0,
      ];
      return $element;
    }
    elseif (count($variations) === 1) {
      /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $selected_variation */
      $selected_variation = reset($variations);
      $element['variation'] = [
        '#type' => 'value',
        '#value' => $selected_variation->id(),
      ];
      return $element;
    }
....
    $variation_options = [];
    foreach ($variations as $option) {
      $variation_options[$option->id()] = $option->label();
    }
    $element['variation'] = [
      '#type' => 'select',
      // Widget settings can't be translated in D8 yet, t() is a workaround.
      '#title' => $this->t($this->getSetting('label_text')),
      '#options' => $variation_options,
      '#required' => TRUE,
      '#default_value' => $selected_variation->id(),
      '#ajax' => [
        'callback' => [get_class($this), 'ajaxRefresh'],
        'wrapper' => $form['#wrapper_id'],
      ],
    ];
πŸ’¬ Support request
Status

Active

Version

11.0 πŸ”₯

Component

jsonapi.module

Created by

πŸ‡ΊπŸ‡ΈUnited States mglaman WI, USA

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024