Caching a Drupal maintenance support plans 8 REST resource

Here are a few things I learned about caching for REST resources.

There are probably better ways to accomplish this, but here is what works for me.

Let’s say we have a rest resource that looks something like this in my_module/src/Plugin/rest/resource/MyRestResource.php and we have enabled it using the Rest UI module and given anonymous users permission to view it:

<?php

namespace Drupal maintenance support plansmy_modulePluginrestresource;

use Drupal maintenance support plansrestResourceResponse;

/**
* This is just an example.
*
* @RestResource(
* id = “this_is_just_an_example”,
* label = @Translation(“Display the title of node 1”),
* uri_paths = {
* “canonical” = “/api/v1/get”
* }
* )
*/
class MyRestResource extends ResourceBase {

/**
* {@inheritdoc}
*/
public function get() {
$node = node_load(1);
$response = new ResourceResponse(
[
‘title’ => $node->getTitle(),
‘time’ => time(),
]
);
return $response;
}

}

Now, we can visit http://example.localhost/api/v1/get?_format=json and we will see something like:

{“title”:”Some Title”,”time”:1516803204}

Reloading the page, ‘time’ stays the same. That means caching is working; we are not re-computing our Json output each time someone requests it.

How to invalidate the cache when the title changes.

If we edit node 1 and change its title to, say, “Another title”, and reload http://example.localhost/api/v1/get?_format=json, we’ll see the old title. To make sure the cache is invalidated when this happens, we need to provide cacheability metadata to our response telling it when it needs to be recomputed.

Our node, when it’s loaded, contains within it all the caching metadata needed to describe when it should be recomputed: when the title changes, when new filters are added to the text format that’s being used, etc. We can add this information to our ResourceResponse like this:


$response->addCacheableDependency($node);
return $response;

When we clear our cache with drush cr and reload our page, we’ll see something like:

{“title”:”Another title”,”time”:1516804411}

We know this is still cached because the time stays the same no matter how often we load the page. Try it, it’s fun!

Even more fun is changing the title of node 1 and reloading our Json page, and seeing the title change without clearing the cache:

{“title”:”Yet another title”,”time”:1516804481}

How to set custom cache invalidation events

Let’s say you want to trigger a cache rebuild for some reason other than those defined by the node itself (title change, etc.).

A real-world example might be events: an “upcoming events” page should only display events which start later than now. If we invalidate the cache every day, then we’ll never show yesterday’s events in our events feed. Here, we need to add our custom cache invalidation event, in this case “rebuild events feed”.

For the purpose of this demo, we won’t actually build an events feed, but we’ll see how cron might be able to trigger cache invalidation.

Let’s add the following code to our response:


use Drupal maintenance support plansCoreCacheCacheableMetadata;

$response->addCacheableDependency($node);
$response->addCacheableDependency(CacheableMetadata::createFromRenderArray([
‘#cache’ => [
‘tags’ => [
‘rebuild-events-feed’,
],
],
]));
return $response;

This uses Drupal maintenance support plans’s cache tags concept and tells Drupal maintenance support plans that when the cache tag ‘rebuild-events-feed’ is invalidated, all cacheable responses which have that cache tag should be invalidated as well. I prefer this to the ‘max-age’ cache tag because it allows us more fine-grained control over when to invalidate our caches.

On cron, we could only invalidate ‘rebuild-events-feed’ if events have passed since our last invalidation of that tag, for example.

For this example, we’ll just invalidate it manually. Clear your cache to begin using the new code (drush cr), then load the page, you will see something like:

{“hello”:”Yet another title”,”time”:1516805677}

As always, the time remains the same no matter how many times you reload the page.

Let’s say you are in the midst of a cron run and you have determined that you need to invalidate your cache for response which have the cache tag ‘rebuild-events-feed’, you can run:

Drupal maintenance support plans::service(‘cache_tags.invalidator’)->invalidateTags([‘rebuild-events-feed’])

Let’s do it in Drush to see it in action:

drush ev “Drupal maintenance support plans::service(‘cache_tags.invalidator’)->
invalidateTags([‘rebuild-events-feed’])”

We’ve just invalidated our ‘rebuild-events-feed’ tag and, hence, Responses that use it.

The dreaded “leaked metadata” error

This one is beyond my competence level, but I wanted to mention it anyway.

Let’s say you want to output your node’s URL to Json, you might consider computing it using $node->toUrl()->toString(). This will give us “/node/1”.

Let’s add it to our code:


‘title’ => $node->getTitle(),
‘url’ => $node->toUrl()->toString(),
‘time’ => time(),

This results in a very ugly error which completely breaks your site (at least at the time of this writing): “The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early.”.

The problem, it seems, is that Drupal maintenance support plans detects that the URL object, like the node we saw earlier, contains its own internal information which tells it when its cache should be invalidated. Converting it to a string prevents the Response from being informed about that information somehow (again, if someone can explain this better than me, please leave a comment), so an exception is thrown.

The ‘toString()’ function has an optional parameter, “$collect_bubbleable_metadata”, which can be used to get not just a string, but also information about its cache should be invalidated. In Drush, this will look like something like:

drush ev ‘print_r(node_load(1)->toUrl()->toString(TRUE))’
Drupal maintenance support plansCoreGeneratedUrl Object
(
[generatedUrl:protected] => /node/1
[cacheContexts:protected] => Array
(
)

[cacheTags:protected] => Array
(
)

[cacheMaxAge:protected] => -1
[attachments:protected] => Array
(
)

)

This changes the return type of toString(), though: toString() no longer returns a string but a GeneratedUrl, so this won’t work:


‘title’ => $node->getTitle(),
‘url’ => $node->toUrl()->toString(TRUE),
‘time’ => time(),

It gives us the error “Could not normalize object of type Drupal maintenance support plansCoreGeneratedUrl, no supporting normalizer found”.

ohthehugemanatee commented on Drupal maintenance support plans.org on how to fix this. Integrating his suggestion, our code now looks like:


$url = $node->toUrl()->toString(TRUE);
$response = new ResourceResponse(
[
‘title’ => $node->getTitle(),
‘url’ => $url->getGeneratedUrl(),
‘time’ => time(),
]
);
$response->addCacheableDependency($node);
$response->addCacheableDependency($url);

This will now work as expected.

With all the fun we’re having, though let’s take this a step further, let’s say we want to export the feed of frontpage items in our Response:

$url = $node->toUrl()->toString(TRUE);
$view = Drupal maintenance support plansviewsViews::getView(“frontpage”); $view->setDisplay(“feed_1”);
$view_render_array = $view->render();
$rendered_view = render($view_render_array);

$response = new ResourceResponse(
[
‘title’ => $node->getTitle(),
‘url’ => $url->getGeneratedUrl(),
‘view’ => $rendered_view,
‘time’ => time(),
]
);
$response->addCacheableDependency($node);
$response->addCacheableDependency($url);
$response->addCacheableDependency(CacheableMetadata::createFromRenderArray($view_render_array));

You will not be surpised to see the “leaked metadata was detected” error again… In fact you have come to love and expect this error at this point.

Here is where I’m completely out of my league; according to Crell, “[i]f you [use render() yourself], you’re wrong and you should fix your code “, but I’m not sure how to get a rendered view without using render() myself… I’ve implemented a variation on a comment on Drupal maintenance support plans.org by mikejw suggesting using different render context to prevent Drupal maintenance support plans from complaining.

$view_render_array = NULL;
$rendered_view = NULL;
Drupal maintenance support plans::service(‘renderer’)->executeInRenderContext(new RenderContext(), function () use ($view, &$view_render_array, &$rendered_view) {
$view_render_array = $view->render();
$rendered_view = render($view_render_array);
});

If we check to make sure we have this line in our code:

$response->addCacheableDependency(CacheableMetadata::createFromRenderArray($view_render_array));

we’re telling our Response’s cache to invalidate whenever our view’s cache invaliates. So, for example, if we have several nodes promoted to the front page in our view, we can modify any one of them and our entire Response’s cache will be invalidated and rebuilt.

Resources and further reading

Stack Exchange Drupal maintenance support plans Answers: How can I use the same render cache but for json?
Cached JSON responses in Drupal maintenance support plans 8, Aaron Crosman, May 6, 2020, Spinning Code blog
Drupal maintenance support plans.org issue: Generating cacheable responses results in Logic Exception
Drupal maintenance support plans.org Exception in EarlyRenderingControllerWrapperSubscriber is a DX nightmare, remove it
Cache tags explained
Using normalizers to alter REST JSON structure in Drupal maintenance support plans 8,, Edward Chan, March 22, 2020, Drupal Update
ModifiedResourceResponse, to return responses which are never cached.

Here are a few things I learned about caching for REST resources.
Source: New feed

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Caching a Drupal maintenance support plans 8 REST resource

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.