8

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\rest\ResourceResponse. in Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (line 154 of /srv/bindings/8c5ccf24ccc9492bb85469725da608ea/code/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php).

I am facing this issue when i try to access REST endpoint through ajax get request. Important thing, when i was logged-in then every works find but when i am not logged it it through above exception.

Here is my code

namespace Drupal\one_rest_api\Plugin\rest\resource;

use Drupal\file\Entity\File;
use Drupal\image\Entity\ImageStyle;
use Drupal\media_entity\Entity\Media;
use Drupal\node\Entity\Node;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\taxonomy\Entity\Term;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;

/**
 * Provides article resource endpoint
 *
 * @RestResource(
 *   id = "one_article_resource",
 *   label = @Translation("One Article Resource"),
 *   uri_paths = {
 *     "canonical" = "/api/one/v1/articles"
 *   }
 * )
 */
class ArticleResource extends ResourceBase
{
    /**
     * A current user instance.
     *
     * @var \Drupal\Core\Session\AccountProxyInterface
     */
    protected $currentUser;

    /**
     * Constructs a Drupal\rest\Plugin\ResourceBase object.
     *
     * @param array $configuration
     *   A configuration array containing information about the plugin instance.
     * @param string $plugin_id
     *   The plugin_id for the plugin instance.
     * @param mixed $plugin_definition
     *   The plugin implementation definition.
     * @param array $serializer_formats
     *   The available serialization formats.
     * @param \Psr\Log\LoggerInterface $logger
     *   A logger instance.
     * @param \Drupal\Core\Session\AccountProxyInterface $current_user
     *   A current user instance.
     */
    public function __construct(
        array $configuration,
        $plugin_id,
        $plugin_definition,
        array $serializer_formats,
        LoggerInterface $logger,
        AccountProxyInterface $current_user) {

        parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);

        $this->currentUser = $current_user;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static(
            $configuration,
            $plugin_id,
            $plugin_definition,
            $container->getParameter('serializer.formats'),
            $container->get('logger.factory')->get('one_rest'),
            $container->get('current_user')
        );
    }
    /**
     * Responds to GET requests.
     *
     * Returns a list of bundles for specified entity.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     *   Throws exception expected.
     */
    public function get() {
        global $base_url;

        $response_result = [];
        $response_code = 200;
        // You must to implement the logic of your REST Resource here.
        // Use current user after pass authentication to validate access.
        if (!$this->currentUser->hasPermission('access content')) {
            throw new AccessDeniedHttpException();
        }

        $entities = \Drupal::entityQuery('node')
            ->condition('status', 1)
            ->condition('type', 'article')
            ->condition('langcode','en')
            ->notExists('field_f1')
            ->sort('created', 'DESC')
            ->range(0, 20)
            ->execute();

        if(!empty($entities)){
            foreach ($entities as $node_id) {
                $node = Node::load($node_id);

                if(is_object($node)){
                    $image_url = '';
                    $image_target = $node->get('field_image')->target_id;
                    if(!empty($image_target)){
                        $media = Media::load($image_target);
                        if(is_object($media)){
                            $image = File::load($media->get('field_image')->target_id);
                            $image_url = ImageStyle::load('rest_api')->buildUrl($image->getFileUri());
                        }
                    }

                    $output_tags = [];
                    $tags = $node->get('field_tags')->getValue();
                    if(!empty($tags)){
                        foreach($tags as $key => $tag){
                            $term = Term::load($tag['target_id']);
                            $output_tags[] = $term->get('name')->value;
                        }
                    }

                    $result = [
                        "nid" => $node->id(),
                        "base_url" => $base_url,
                        "title" => $node->getTitle(),
                        "field_image" => $image_url,
                        "field_tags" => $output_tags
                    ];

                    $response_result[] = $result;
                }

            }
        }


        if(!empty($response_result)) {
            $headers = [
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PATCH, DELETE',
                'Access-Control-Allow-Headers' => 'Authorization'
            ];

            $response = new ResourceResponse($response_result, $response_code, $headers);
            return $response;
        }

        throw new HttpException(t("Empty Response"));
    }
}

And Below is my typescript code

load(){

    if(this.data){
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {
      this.http.get('https://example.com/api/one/v1/articles?_format=json')
        .subscribe(data => {
          this.data = data;
          resolve(this.data);
        },err => {
          console.log(err);
        });
    });

  }

I didn't understand what i am missing in drupal code

8
  • 1
    See drupal.stackexchange.com/questions/225956/… for how to avoid this for normal urls. You can try something like this for the image style. If you don't get this working, then another option is to wrap the code in your own render context, see drupal.stackexchange.com/questions/245715/…
    – 4uk4
    Dec 10, 2017 at 16:57
  • Tried that but that didn't help me
    – Udit Rawat
    Dec 10, 2017 at 17:48
  • 1
    Sorry not to be able to provide an easy answer, this is really a DX nightmare. You might need to wait until image styles are provided in the image field to get an easier solution drupal.org/project/drupal/issues/2825812.
    – 4uk4
    Dec 10, 2017 at 17:57
  • @4k4 I believe \Drupal::entityQuery('node') creating cache / context or something else which is causing metadata issue.
    – Udit Rawat
    Dec 10, 2017 at 18:12
  • 3
    Then try to skip the access checks: ->accessCheck(FALSE)
    – 4uk4
    Dec 10, 2017 at 18:54

4 Answers 4

8

Modify your entity query to something like below

$entities = \Drupal::entityQuery('node')
            ->condition('status', 1)
            ->condition('type', 'article')
            ->condition('langcode','en')
            ->notExists('field_f1')
            ->sort('created', 'DESC')
            ->range(0, 20)
            ->accessCheck(false)   
            ->execute();
2
  • 2
    Although this may work I don't believe disabling access check is the correct way to do deal with the problem, is it?
    – LarS
    Sep 1, 2020 at 4:09
  • accessCheck(false) resolved my issue, it's because you can have a hook like a node_access who checks entity access and add metadata, with accessCheck(false) you skip this hook. Aug 23, 2022 at 8:57
13

I have solved it by adding code at the end because class ResourceResponse is using CacheableResponseTrait which is responsible to cache metadata. In POST, PATCH, DELETE we don't want that because resources are modified.

return new ModifiedResourceResponse($node);
1
  • 2
    +1 - Remember to add use Drupal\rest\ModifiedResourceResponse. Jan 15, 2021 at 16:33
3

I think the answers that are currently listed are the proper methods to follow. However, I think its relevant to note, that I was able to fix this (possibly temporarily) by reinstalling the jsonapi module in drupal using composer. In case it may help someone, this is what I ran from the command line:

composer require 'drupal/jsonapi:^1.24'

Don't forget to change the code to the version you are trying to install. As you can see, this is specifically for version 1.24.

1
  • thank you, works for me.
    – mana
    Sep 29, 2020 at 4:49
3

For me, wrapping the query in a render context fixed the issues without having to define the the accessCheck(FALSE):

// Wrap Query in render context.
$context = new RenderContext();
$nids = \Drupal::service('renderer')->executeInRenderContext($context, function () {
  // Query some nodes.
  $my_query = \Drupal::entityQuery('node')
    ->condition('type', 'my_type')
    ->condition('status', 1);
  return $my_query->execute();
});

https://www.lullabot.com/articles/early-rendering-a-lesson-in-debugging-drupal-8

2
  • I encountered this error while using GraphQL to pull entities for an entity-reference field configured to use views-entity-reference result type. This response was the best option for me. Thanks.
    – bfuzze
    May 2, 2022 at 15:41
  • Thanks @scott this worked for me.
    – Amit
    Jul 18, 2022 at 5:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.