2

I'd like to create a module capable of overriding some of the theme's css (for any theme). Think of small things like hiding the image, changing font sizes and colors, etc.

My initial thought was to toggle this using a session variable:

/**
 * Implements hook_library_info_build().
 */
function my_module_library_info_build() {
  $libraries = [];
  $libraries['override'] = [
    'version' => '1.0',
    'css' => [
      'base' => [
        'override.css' => [],
      ],
    ],
  ];
  return $libraries;
}

/**
 * Implements hook_preprocess_HOOK().
 */
function my_module_preprocess_page(&$variables) {
  if ($_SESSION['override']['enabled']) {
    $variables['#attached']['library'][] = 'my_module/override';
  }
}

This works very well for logged in users, but for anonymous users it only caches the first version of the page (using the status of $_SESSION['override']['enabled'] at that time). So some pages show the regular lay-out, and some only show the overridden version.

I realized that I probably have to add a cache tag, so I tried adding

$variables['page']['#cache']['tags'][] = 'override:' . $_SESSION['override']['enabled'];

Assuming this would allow 2 versions of the page to be cached, either for override enabled or disabled. But that does not work either.

What needs to be done to allow multiple versions of the same page to be cached and served, depending on a $_SESSION variable?

3

Related: Cache context drupal don't work.

But your logic is actually depending on the session, and the internal page cache is disabled automatically for users with a session.

So it is more likely that you are having a problem with the dynamic page cache.

Your problem is that cache tags are not about caching multiple variations. They are about invalidating already cached things when something changes. So if you have configuration which override value should load which value, then you'd need a cache tag for that, so you can invalidate it when that happens. Also in 95%+ of the cases, you don't need to make up your own cache tags, but just use the default cache tags for entities, config objects and so on.

What you want is a cache context, either the default 'session' or make your own, that doesn't vary for the whole session content but only your key, whould is more efficient. See https://www.drupal.org/docs/8/api/cache-api/cache-contexts

And you should also read https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays to understand when you need cache tags, contexts, ..

  • Thanks for your reaction. I read over that documentation multiple times, but still have trouble figuring out what to use. I was advised to use tags in this question but I now understand those should be contexts as well? I assumed that having too many contexts would result in a lot of different caches being created and that it should be used sparingly. The current problem is only with anonymous users using a static cache, which according to your first link have no access to cache contexts. Won't overriding PageCache clach with other modules? – Neograph734 Jan 30 '17 at 20:08
  • Yes it will, you shouldn't overrride it from a contrib module, but it's not something that needs to be done often. Try disabling internal page cache and dynamic page cache to see what makes your problem go away. Might work for authenticated because something else already adds a context for them to be per-user/session. – Berdir Jan 30 '17 at 20:16
  • The other answer is correct, as I wrote, tags are for invalidation and contexts for variations. And yes of course, the more contexts you have the more variations you have but that's the whole point of them. Your other option is to disable caching completely for those/all pages or for example disable dynamic page cache. Or you move the logic into JS, and always add the library, but combine it with a class on the body that you add with JS. You need to add it as a separate cookie then, not session. – Berdir Jan 30 '17 at 20:18
  • Then I suppose the other question was phrased wrong. That was about allowing multiple variations of the page with different white labels. And invalidation them once the user updates his white label entity. I now realise I need a combination of context and tags for that. I will play around with this for a little. Thanks again. – Neograph734 Jan 30 '17 at 20:25
1

Mostly thanks to Berdir I have managed to solve this problem.

I first implemented a custom cache context session.mymodule_override (declared as a service) that is supplied with the value of the $_SESSION:

my_module.services.yml

cache_context.mymodule_override:
  class: Drupal\my_module\Cache\Context\MyModuleCacheContext 
  arguments: ['@request_stack']
  tags:
    - { name: cache.context }

src/Cache/Context/MyModuleCacheContext.php

class MyModuleCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {

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

  /**
   * {@inheritdoc}
   */
  public function getContext() {
    return isset($_SESSION['override']['enabled']) ? $_SESSION['override']['enabled'] : 0;
  }

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

}

But that still did not work.

Then suddenly I realized that my_module_preprocess_page() was an invocation of hook_preprocess_HOOK() and that those hooks are probably not called when serving a cached page. So I swapped it with hook_page_attachments():

my_module.module

function my_module_page_attachments(array &$attachments) {
  $attachments['#cache']['contexts'][] = 'session.mymodule_override';

  if ($_SESSION['my_module']['enabled']) {
    $attachments['#attached']['library'][] = 'my_module/override';
  }
}

It works flawless now :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.