Not only #cache['keys'], now also#cache['contexts'] for a better DX
Cache contexts were introduced in https://www.drupal.org/node/2222293. They were regarded as "special keys". But this was both bad DX-wise (keys and contexts intermingled) and problematic (it's quite likely somebody will ever generate a key that exactly matches the "special key" for a cache context, and hence getting an unexpected cache ID).
- Before
-
$build = [ '#pre_render' => 'heavy_calculations', '#cache' => [ 'keys' => [ 'some_thing', $thing->id(), // The thing is rendered differently depending on your permissions. 'cache_context.user.roles', ], ], ]; - After
-
$build = [ '#pre_render' => 'heavy_calculations', '#cache' => [ 'keys' => [ 'some_thing', $thing->id(), ], 'contexts' => [ // The thing is rendered differently depending on your permissions. 'user.roles', ], ], ];
Cache contexts now bubble
If a parent uses only the 'foo' cache context (and is hence varied only by that context) but a child of that parent is also varied by the 'bar' context, then cache context bubbling ensures that the parent is automatically varied by both 'foo' and 'bar', to ensure all its actual variations are reflected as expected.
See #2429257: Bubble cache contexts for details.
Consequently, just like we have the X-Drupal-Cache-Tags header for cache tags, we now also have a X-Drupal-Cache-Contexts header.
Added support for parameter-dependent cache contexts: CalculatedCacheContextInterface
Before #2428563: Introduce parameter-dependent cache contexts, some cache contexts could not be expressed as … cache contexts. That forced developers to use the semantically wrong alternative: cache keys. This was a big DX problem, because it was very confusing.
But it also would have had long-term negative effects for Drupal 8: it'd have prevented many Drupal 8 contrib modules to not set the correct caching metadata, hence causing incorrect caching, because cache contexts will soon bubble (see #2429257: Bubble cache contexts), and cache keys won't.
- Note!
- This example uses rendered menus as a means to explain the problem. It's not limited to menus. This example applies to anything where there's multiple of a thing: every menu has an active trail (hence a parameter is necessary to identify which menu to get the active trail cache context for). Compare that to the existing cache contexts, of which there is always only one: for every request, there is only one current user, current user's set of roles, current request's URL, and so on.
- Before
-
$menu_active_trail_cache_key = MenuActiveTrail::getActiveTrailCacheKey($menu_name); $build = [ '#pre_render' => 'heavy_calculations', '#cache' => [ 'keys' => [ 'menu', $menu->id(), // The menu is rendered differently depending on the active trail. $menu_active_trail_cache_key ], 'contexts' => [ 'language', … ], ], ]; - After
-
$build = [ '#pre_render' => 'heavy_calculations', '#cache' => [ 'keys' => [ 'menu', $menu->id(), ], 'contexts' => [ 'language', …, // The menu is rendered differently depending on the active trail. // Note the double colon before the parameter! 'menu.active_trail:' . $menu->id(), ], ], ];