Problem/Motivation

The response object returned to the browser must contain all cacheability metadata or EarlyRenderingControllerWrapperSubscriber throws a nasty exception

Proposed resolution

Pass TRUE and add the GeneratedUrl object as a cacheable dependency to the response object. This also needs that you use the Response object like e.g. https://github.com/bartfeenstra/drupal-payment/pull/20.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#2 payment-cacheability-2630808-2.patch687 bytesberdir

Comments

Berdir created an issue. See original summary.

berdir’s picture

Status: Active » Needs review
StatusFileSize
new687 bytes
johnchque’s picture

xano’s picture

Status: Needs review » Postponed

Thank you for fixing this issue! I'm marking this as postponed pending the PR at https://github.com/bartfeenstra/drupal-payment/pull/29. Once that has been merged, we can close this issue.

xano’s picture

Status: Postponed » Fixed

I like this! Committed and pushed to 8.x-2.x. Thanks a lot!

  • Xano committed f1cf69c on 8.x-2.x authored by yongt9412
    Issue #2630808 by Berdir, yongt9412: Response::getResponse() does not...

Status: Fixed » Closed (fixed)

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

jurgenhaas’s picture

@Berdir I'm just coming across the same error and tried this patch/workaround, with no luck.

First try was to update from beta3 to 2.x-dev release as the patch above is included there, but that didn't solve the problem.

Second try then was to put this into my controller:

      $response = $result->getCompletionResponse();
      $url = $response->getRedirectUrl()->toString(TRUE);
      return new TrustedRedirectResponse($url->getGeneratedUrl());

But I still get this error:

LogicException: The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\Core\Routing\TrustedRedirectResponse. in Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (line 154 of core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php).
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
call_user_func_array(Object, Array) (Line: 144)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 64)
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: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 628)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Any chance that you could help me with this? If you prefer, we can do that on PM or Slack or which ever channel you prefer.

berdir’s picture

This only works if whoever creates the Url adds the necessary cacheablity metadata and only if it is really the URL.

For example I had a bug with search_api_solr that created a URL for the solr connection and that made it fail. You need to debug the cacheability metadata you have and see if you can track that down to the module that is causing it.

jurgenhaas’s picture

Sorry, I'm not getting it. I use the payment module and I have a controller that creates and executes the payment object and then wants to redirect to the external URL given by the payment module. The controller looks like this:

class FimfPayment extends ControllerBase {

  public function execute(UserInterface $user) {
     [...]
    $result = $this->payment->execute();
    $response = $result->getCompletionResponse();
    return $response->getResponse();
  }

}

So, that returns a TrustedRedirectResponse instance to EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext() which checks if the response is instance of CacheableResponseInterface and if so, always throws the exception. And any TrustedRedirectResponse instance is of type CacheableResponseInterface and so I have no idea how to prevent the error.

What am I missing?

jurgenhaas’s picture

Now I found it. I'm testing with the payment method https://www.drupal.org/project/mollie_payment and they are calling Drupal::url() to generate the return URLs and that calls some render function. I've now replaced that with Url()->toString(TRUE)->getGeneratedUrl() and now it is working because the render context remains empty.

Phew, that is somehow dangerous. It means that any other contrib module can break a lot if they call any function that inheritly calls any render function, maybe even by mistake.

berdir’s picture

Yes, ->execute() does something, probably create Url objects and so on. Those are added to the global stack of cache metadata and then not handling it results in this.

if it is a contrib payment method, report a bug there, if it is your own, you'll have to debug it yourself. Contact us (info@md-systems.ch) if you want to pay us to look into it, I can't see more without seeing the code/being able to debug it.

jurgenhaas’s picture

It's not mine but I submitted a patch to the module, see #2802999: Do not use Drupal::url() any longer

StephanW’s picture

@Berdir:
I'm trying to use the payment datatrans module and am now also stuck at this error and don't know how to avoid it.

I just installed the needed modules and my controller does this:

    $payment = \Drupal::entityTypeManager()->getStorage('payment')->create([
      'bundle' => 'payment_reference'
    ]);
    $payment->setCurrencyCode('EUR')
      ->setPaymentMethod(\Drupal\payment\Payment::methodManager()->createInstance('payment_datatrans:payment_datatrans'))
      ->setOwnerId(1);

    $lineItemManager = \Drupal\payment\Payment::lineItemManager();

    $lineItemsArray = [];

    $lineItemsArray[]= $lineItemManager->createInstance('payment_basic')
        ->setName('Iron shirts')
        ->setDescription('Iron shirts')
        ->setAmount(2)
        ->setCurrencyCode('EUR')
        ->setQuantity(1);
	.
	.
	.

    $payment->setLineItems($lineItemsArray);

    $payment->save();
    $result = $payment->execute();

    $response = $result->getCompletionResponse();
    return $response->getResponse();



I also tried

return new TrustedRedirectResponse($result->getCompletionResponse()->getRedirectUrl()->toString(TRUE)->getGeneratedUrl());

and got the same error message.

What am I doing wrong?

pounard’s picture

I finally ended up with this very ugly solution:

    public function goToSite(/* some parameters */)
    {
        $url = new Url('some url outside the drupal site', [], [ /* ... */]);

        // What the fuck Drupal, seriously.
        // @see
        //   https://www.drupal.org/node/2630808
        //   https://drupal.stackexchange.com/questions/225956/cache-controller-with-json-response
        //   ... and many others.
        return new TrustedRedirectResponse($url->toString(true)->getGeneratedUrl());
    }

Which is an imperial DX failure.