If commerce_product has an entity reference to a custom translatable entity that has its translations overview available under /product/{commerce_product}/custom_entity/{custom_entity}/translations ProductVariationContext runs into a fatal error. In our case the custom entity is named description and the links look like this:

 *     links={
 *         "edit-form": "/product/{commerce_product}/descriptions/{jarltech_commerce_description}/edit",
 *         "collection": "/product/{commerce_product}/descriptions",
 *         "drupal:content-translation-overview": "/product/{commerce_product}/descriptions/{jarltech_commerce_description}/translations",
 *         "drupal:content-translation-add": "/product/{commerce_product}/descriptions/{jarltech_commerce_description}/translations/add/{source}/{target}",
 *         "drupal:content-translation-edit": "/product/{commerce_product}/descriptions/{jarltech_commerce_description}/translations/edit/{language}",
 *         "drupal:content-translation-delete": "/product/{commerce_product}/descriptions/{jarltech_commerce_description}/translations/delete/{language}",
 *     },

When you access an URL like /product/51/descriptions/18543/translations that leads to a fatal PHP error:

TypeError: Argument 1 passed to Drupal\commerce_product\ProductVariationStorage::loadFromContext() must implement interface Drupal\commerce_product\Entity\ProductInterface, string given, called in /var/www/html/web/modules/contrib/commerce/modules/product/src/ContextProvider/ProductVariationContext.php on line 79 in Drupal\commerce_product\ProductVariationStorage->loadFromContext() (line 94 of modules/contrib/commerce/modules/product/src/ProductVariationStorage.php).

Drupal\commerce_product\ProductVariationStorage->loadFromContext('51') (Line: 79)
Drupal\commerce_product\ContextProvider\ProductVariationContext->getRuntimeContexts(Array) (Line: 127)
Drupal\commerce_product\ContextProvider\ProductVariationContext->getAvailableContexts() (Line: 97)
Drupal\Core\Plugin\Context\LazyContextRepository->getAvailableContexts() (Line: 114)
Drupal\Core\ParamConverter\EntityConverter->convert('51', Array, 'commerce_product', Array) (Line: 100)
Drupal\Core\ParamConverter\ParamConverterManager->convert(Array) (Line: 45)
Drupal\Core\Routing\Enhancer\ParamConversionEnhancer->enhance(Array, Object) (Line: 244)
Drupal\Core\Routing\Router->applyRouteEnhancers(Array, Object) (Line: 118)
Drupal\Core\Routing\Router->matchRequest(Object) (Line: 92)
Drupal\Core\Routing\AccessAwareRouter->matchRequest(Object) (Line: 223)
Drupal\system\PathBasedBreadcrumbBuilder->getRequestForPath('/product/51/descriptions', Array) (Line: 169)
Drupal\system\PathBasedBreadcrumbBuilder->build(Object) (Line: 83)
Drupal\Core\Breadcrumb\BreadcrumbManager->build(Object) (Line: 72)
Drupal\system\Plugin\Block\SystemBreadcrumbBlock->build() (Line: 171)
Drupal\block\BlockViewBuilder::preRender(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 781)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 372)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 444)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 200)
Drupal\Core\Render\Renderer->render(Array) (Line: 450)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 43)
__TwigTemplate_f70e11ab7be0e27ffc93fe3335c7a51dc29180b04eec73e284f0179228ddc9de->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 65)
twig_render_template('core/themes/claro/templates/page.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('page', Array) (Line: 431)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 200)
Drupal\Core\Render\Renderer->render(Array) (Line: 450)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 86)
__TwigTemplate_06794b46e2912d1a57d8b6d98fa46703d63c4c8a445dd14b0e30ed921e7bb3b4->doDisplay(Array, Array) (Line: 405)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 378)
Twig\Template->display(Array) (Line: 390)
Twig\Template->render(Array) (Line: 65)
twig_render_template('core/themes/claro/templates/classy/layout/html.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('html', Array) (Line: 431)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 200)
Drupal\Core\Render\Renderer->render(Array) (Line: 162)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 163)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 163)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 706)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

The issue is caused by core's PathBasedBreadcrumbBuilder::getRequestForPath(). The breadcrumb builder "reduces" the URL step by step and creates "fake" routes and checks if they are valid:

  protected function getRequestForPath($path, array $exclude) {

    ...

    // Attempt to match this path to provide a fully built request.
    try {
      $request->attributes->add($this->router->matchRequest($request));
      return $request;
    }

    ...

  }

That testing of /product/51/descriptions when visiting /product/51/descriptions/18543/translations leads to a fatal error in ProductVariationContext::getRuntimeContexts() because you don't get a product entity but the product ID from the route match.

Issue fork commerce-3200588

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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mkalkbrenner created an issue. See original summary.

mkalkbrenner’s picture

Status: Active » Needs review
cspitzlay’s picture

Status: Needs review » Reviewed & tested by the community

This patch makes sense to me and it solves the above crash.
We have been using it with success since February.

jsacksick’s picture

Status: Reviewed & tested by the community » Fixed

I believe this wouldn't be occurring anymore since #3185884: Layout Builder for product with empty variation field not working is in.

However #3185884: Layout Builder for product with empty variation field not working did not cover the case of a route where the product ID was passed as a string.

The patch no longer applied, so I rerolled it, made minor changes to it and committed it.

  • jsacksick committed 007444c on 8.x-2.x
    Issue #3200588 by mkalkbrenner, cspitzlay: Core BreadcrumbBuilder might...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

mglaman’s picture

While working on #3220015: ProductVariationContext improperly implements ContextProviderInterface, I think the root issue is due to how the context provider is implemented. Whenever entity param conversion occurred, the full runtime context value was evaluated. When only a stub should have been returned. Relates to #3182636: Layout builder for products cannot render extra fields. Working on a fuller fix which would revert the changes made here, but have a more complete fix.

mglaman’s picture

Proof:

Given that this returned an ID and not an upcasted parameter, shows it was running out of step:

    if ($value === NULL) {
      $product = $this->routeMatch->getParameter('commerce_product');
      if ($product instanceof ProductInterface) {

The route match hadn't been fully populated or calculated. See the relevant bits here in the backtrace:

Drupal\commerce_product\ContextProvider\ProductVariationContext->getAvailableContexts() (Line: 97)
Drupal\Core\Plugin\Context\LazyContextRepository->getAvailableContexts() (Line: 114)
Drupal\Core\ParamConverter\EntityConverter->convert('51', Array, 'commerce_product', Array) (Line: 100)
Drupal\Core\ParamConverter\ParamConverterManager->convert(Array) (Line: 45)
Drupal\Core\Routing\Enhancer\ParamConversionEnhancer->enhance(Array, Object) (Line: 244)
Drupal\Core\Routing\Router->applyRouteEnhancers(Array, Object) (Line: 118)
Drupal\Core\Routing\Router->matchRequest(Object) (Line: 92)
Drupal\Core\Routing\AccessAwareRouter->matchRequest(Object) (Line: 223)
Drupal\system\PathBasedBreadcrumbBuilder->getRequestForPath('/product/51/descriptions', Array) (Line: 169)
Drupal\system\PathBasedBreadcrumbBuilder->build(Object) (Line: 83)
Drupal\Core\Breadcrumb\BreadcrumbManager->build(Object) (Line: 72)
Drupal\system\Plugin\Block\SystemBreadcrumbBlock->build() (Line: 171)

The problem is at getAvailableContexts.