Cache a block by a custom entity rendered within I struggle with getting a custom block cached correctly:


we have a custom entity "Politician"
For that entity we have several routes where we want to render a "politician header" with some basic information about the politician
the politician header is a custom template which is rendered in a custom block
the problem is: when caching is activated the block always shows the politician data from the first politician which was rendered. 


Here is the code for the block plugin - to keep it simple I do not copy the method getPoliticianFromUrl() here:

/**
 * Provides a block with the profile header for a politician. We do not render
 * it on the public edit form path
 *
 * @Block(
 *   id = "aw_frontend_politician_header",
 *   admin_label = @Translation("Politician profile header"),
 *   category = "Abgeordnetenwatch"
 * )
 */
class PoliticianProfileHeader extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    $politician = $this-&gt;getPoliticianFromUrl();
    if ($politician == NULL) {
      return [];
    }

    return array(
      '#theme' =&gt; 'politician_header',
      '#politician' =&gt; $politician,
      '#cache' =&gt; [
        'keys' =&gt; ['politician', $politician-&gt;id()]
      ]
    );
  }

  public function getCacheContexts() {
    $politician = $this-&gt;getPoliticianFromUrl();
    if ($politician != NULL) {
      return Cache::mergeContexts(parent::getCacheContexts(), $politician-&gt;getCacheContexts());
    }
    return parent::getCacheContexts();
  }

  public function getCacheTags() {
    $politician = $this-&gt;getPoliticianFromUrl();
    if ($politician != NULL) {
      return Cache::mergeTags(parent::getCacheTags(), $politician-&gt;getCacheTags());
    }
    return parent::getCacheTags();
  }
}


Then I render the politician header - again I just put in interesting parts:

function template_preprocess_politician_header(&amp;$variables) {
  /** @var \Drupal\pw_basic\Entity\Politician $politician */
  $politician = $variables['politician'];
  /** @var \Drupal\Core\Render\Renderer $renderer */
  $renderer = \Drupal::service('renderer');
  $renderer-&gt;addCacheableDependency($variables, $politician);
  $variables["#cache"]["contexts"][] = 'user.roles';
  $variables['#cache']['keys'] = ['politician_header', $politician-&gt;id()];
}


I thought when I define cache keys this ensures that different versions of the render array are cached. This does not happen - not for the custom politician header template (for which I do not find any cached data in cache_render tanle - is this just cached with Twig caching?) and not for the block.

My idea was to create a custom cache context based on my Politician entity but as far as I understand cache contexts they depend on more global circumstances such as the active theme, the current user and things like that. I hoped the Entity API would generate such a context in a magical way so I tried $politician->getCacheContexts() but nothing.

What do I miss?
By any reason I do not really get the block rendering to respect my cache keys set wherever. It only respects set cache contexts so I create a custom cache context:

In pw_basic.services.yml (pw_basic is the name ofthe module)

  cache_context.politician:
    class: Drupal\pw_basic\Cache\PoliticianCacheContext
    arguments: ['@current_route_match']
    tags:
      - { name: cache.context }


The politician cache context class:

class PoliticianCacheContext implements CacheContextInterface {

  use GetPoliticianFromUrlTrait;

  /**
   * @var \Drupal\pw_basic\Entity\Politician
   */
  protected $politician;

  public function __construct(CurrentRouteMatch $current_route_match) {
    $this-&gt;politician = $this-&gt;getPoliticianFromUrl();
  }


  /**
   * {@inheritdoc}
   */
  public static function getLabel() {
    return t('Politician');
  }

  /**
   * {@inheritdoc}
   */
  public function getContext() {
    $cache_context = '';
    if ($this-&gt;politician !== NULL) {
      $cache_context = $this-&gt;politician-&gt;id();
    }
    return $cache_context;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }
}


Then I cleared the block plugin class from any cache logic and just added the following in template_preprocess_politician_header(), the preprocess function of the template rendered within the block:

$variables["#cache"]["contexts"][] = 'politician';


What I do not really like is the service character of the cache context classes. Why is it only possible to set the context in such a global way? Do I miss somethin? I need to use this global workaround and check for the politician from URL which makes this context not as flexible as I want it to be. Is there a way to give a variable to a cache context directly when defining it in a render array?