Drupal 8: Change Commerce Product Variation URL to SKU

To pull this off, we need to write a custom module.

Let's name our module commerce_variation_sku.

In our commerce_variation_sku.module file, we need implement the hook hook_entity_type_build to change the entity class and entity storage class to our custom classes.

/**
 * Implements hook_entity_type_build().
 */
function commerce_variation_sku_entity_type_build(array &$entity_types) {
  if (isset($entity_types['commerce_product_variation'])) {
    $entity_types['commerce_product_variation']->setClass('Drupal\commerce_variation_sku\Entity\ProductVariation');
    $entity_types['commerce_product_variation']->setStorageClass('Drupal\commerce_variation_sku\ProductVariationStorage');
  }
}

Next, we create our custom entity class that will extend the original entity class.

<?php

namespace Drupal\commerce_variation_sku\Entity;

use Drupal\Core\Url;
use Drupal\commerce_product\Entity\ProductVariation as ProductVariationBase;

class ProductVariation extends ProductVariationBase {
  /**
   * {@inheritdoc}
   */
  public function toUrl($rel = 'canonical', array $options = []) {
    // Product variation URLs depend on the parent product.
    if (!$this->getProductId()) {
      // RouteNotFoundException tells EntityBase::uriRelationships()
      // to skip this product variation's link relationships.
      throw new RouteNotFoundException();
    }

    // StringFormatter assumes 'revision' is always a valid link template.
    if (in_array($rel, ['canonical', 'revision'])) {
      $route_name = 'entity.commerce_product.canonical';
      $route_parameters = [
        'commerce_product' => $this->getProductId(),
      ];
      $options += [
        'query' => [
          'sku' => $this->getSku(),
        ],
        'entity_type' => 'commerce_product',
        'entity' => $this->getProduct(),
        // Display links by default based on the current language.
        'language' => $this->language(),
      ];
      return new Url($route_name, $route_parameters, $options);
    }
    else {
      return parent::toUrl($rel, $options);
    }
  }
}

 

Then we create our custom entity storage class that again extends the original entity storage class.

<?php

namespace Drupal\commerce_variation_sku;

use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\commerce_product\ProductVariationStorage as ProductVariationStorageBase;

/**
 * Defines the product variation storage.
 */
class ProductVariationStorage extends ProductVariationStorageBase {
  /**
   * {@inheritdoc}
   */
  public function loadFromContext(ProductInterface $product) {
    $current_request = $this->requestStack->getCurrentRequest();
    if ($sku = $current_request->query->get('sku')) {
      $variation = $this->loadBySku($sku);
      if (in_array($variation->id(), $product->getVariationIds())) {
        /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $variation */
        if ($variation->isPublished() && $variation->access('view')) {
          return $variation;
        }
      }
    }
    return $product->getDefaultVariation();
  }
}

Last step is to clear your cache and you're good to go 😀