Caching Data

Here's a simple way to cache data if the item itself has a lot of logic that bogs the server down...

Let's say you run a batch process and build a catalog item for an e-comm store.

Your 'foreach' would be converted into a batch process, but for sake of explaining caching here, we'll assume that a 'foreach' loop wouldn't timeout. :)

foreach ($products as $product) {
	// Again, do not use a foreach for large data sets.
	generateProductTile($product);
}

/**
 * Generates product tiles with a lot of logic behind how they are displayed
 */
function generateProductTile($product) {
	$drupalCache = \Drupal::cache();
	$cid = 'my_module:product_tile: . ' $product->id;
	// If we've already cached this result, we don't have to do any
	// excessive processing.
	if ($cached = $drupalCache->get($cid)) {
		if (!empty($cached)) {
			return $cached->data;
		}
	}
	
	// If we don't have a cache object, we do whatever we need to build the output.
	$title = $product->label();
	$price = $product->price();
	
	// Maybe we add some logic to the price, and this process is pretty intensive.
	$price = change_price($price);
	
	// Maybe we need to get an image... e-comm entities have weird ways of storing images
	// so this may also take a while.
	$image = get_product_image($product);
	
	// Let's pretend we're done... now we can render this tile.
	$themedTile = [
		'#theme' => 'product_tile',
		'#data' => [
			'title' => $title,
			'price' => $price,
			'image' => $image
		]
	];
	
	$rendererService = \Drupal::service('renderer');
	// This is just a basic way to render the basic html for this item
	// based on a hook_theme entry.
	$renderedTile = rendererService->renderRoot($themedTile);
	
	// Now that we did all that work, lets cache it so if we run this again
	// we don't have to do all that logic again. We can just gather pre-rendered tiles.
	// Setting the tag implies that when you save or update a product entity
	// this cache will be invalidated and rebuild itself the next time it's called.
	// (Double check the tag format for the entity)
	$drupalCache->set(cid: $cid, data: $renderedTile, tags: ['product:' . $product->id()]);
	
	// In the ->set method, you can set the expiration as well. By default it is 'permanent'
	// If you want some other caching policy, do that.
	
	// Since we want the built item, return it.
	return $renderedTile;
}

As an added bonus, here's the hook_theme  in the .module file, and the twig format to render this.

<?php

/**
 * Implements hook_theme().
 */
 function MY_MODULE_theme() {
 	return [
 		'product_tile' => [
 			'variables' => ['data' => NULL],
 			'template' => 'product_tile' /* This assumes the file is in the /templates directory within your module */
 		]
 	];
 }

And your twig file in MY_MODULE/templates directory.

** the image could also be pre-rendered, but this is more to show how you can use parts of variables as data within your twig file.

<h1>{{ data.title }}</h1>
<div class="price">{{ data.price }}</div>
<img src="{{ data.image.src }}" alt="{{ data.image.alt }}">