Change record status: 
Project: 
Introduced in branch: 
9.2.x
Introduced in version: 
9.2.0
Description: 

Fairly often there is a need for some sort of contextual data when rendering an element, which parts of, ultimately needs to make its way down the theme system pipeline.

This is especially true when certain render elements or theme hooks, that are not normally aware of the context in which they'll be rendered, needs additional information to provide rendering variations (i.e. per type or identifier).

While it is possible to add any variable you want in preprocess functions, it is often too late in the process and the original data that usually contains the needed information has long since been lost.

This is, in part, due to arbitrary properties (not registered in the theme hook) from being automatically transferred from a renderable element's properties to the variables array.

Now, every render element will have a default empty array property named #context and every theme hook have a default empty array property named context.

Whenever #context is provided in the creation of a render array, its values are passed along to the context property in variables so they can be used (in preprocessing only) however is needed.

Deprecated theme variable

The $variables['theme_hook_original'] variable is now deprecated and will be removed in a future release. Any existing preprocess functions that utilize this variable should be updated to use $variables['context']['theme_hook_original'].

The $variables['context'] property is removed just prior to rendering to prevent any potential security leaks should one accidentally pass an entire object which may contain sensitive information (e.g. a user). Subsequently, if one needs information from the passed context, they must extract it or manually expose it as a separate variable in the preprocess layer.

Inline templates will continue to follow Twig nomenclature and use #context for passing variables to the inline template. While the render array property is similarly named, it is not affected by this change because inline templates are prerendered into markup and are never preprocessed or passed to an actual template.

Example

Pass #context when creating render arrays:

$build['list'] = [
  '#theme' => 'item_list',
  //...
  '#context' => [
    'entityId' => $entity->id(),
    'entityTypeId' => $entity->getEntityTypeId(),
  ],
];

Consume passed context by extracting it from $variables['context']:

/**
 * Implements hook_preprocess_HOOK().
 */
function THEMENAME_preprocess_item_list(&$variables) {
  // Add an entity bundle specific class. 
  if (!empty($variables['context']['entityTypeId']) && empty($variables['context']['entityId'])) {
    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = \Drupal::service('entity_type.manager')->getStorage($variables['context']['entityTypeId'])->load($variables['context']['entityId']);
    $variables['attributes']['class'][] = \Drupal\Component\Utility\Html::getClass('entity--' . $entity->bundle());
  }
}

While it is possible to pass entire objects as context, it is highly recommended to pass only scalar values (such as an entity type and an identifier to reconstruct the entity on a per needed basis). This will maximize compatibility, reduce database size and risk of any future complications from potential API changes. If you plan on or need to pass an entire object, ensure the object can be properly serialized in the database. Render caching may store the entire render array, which will include objects inside #context. The easiest way to ensure an object can be serialized is to use the DependencySerializationTrait, however doing this may not be enough and may still lead to unforeseen complications.

Impacts: 
Module developers
Themers