3

I’m working on a project where users can create a book (core functionality). In a simple text field they can connect a domain to this book. This way the book is only accessible via this domain.

For example I have Book A with path node\1 and with domain a.book.com. I also have Book B with path node\2 and connected to domain b.book.com.

What I want now is if a visitor goes to a.book.com the frontpage is node/1. And if they go to b.book.com the frontpage should be node/2? I created a custom module for this book_frontpage.

For this I used large parts of the PathProcessorFront.php from core.

The code of my book_front.services.yml is:

services:
  book_frontpage.path_processor_front:
    class: Drupal\book_frontpage\PathProcessor\FrontPagePathProcessor
    tags:
      - { name: path_processor_inbound, priority: 300 }

The code of my FrontPagePathProcessor.php is:

<?php

namespace Drupal\book_frontpage\PathProcessor;

use Drupal\Core\Database\Database;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Path processor to replace 'node' with 'content' in URLs.
 */
class FrontPagePathProcessor implements InboundPathProcessorInterface{
  /**
   * {@inheritdoc}
   */
  public function processInbound($path, Request $request)
  {
    if ($path === '/') {
      //current domain
      $currentDomain = $_SERVER['SERVER_NAME'];

      //get nid of reader where domain is set
      $nid = Database::getConnection()->select('node__field_domain', 'd')
        ->fields('d', ['entity_id'])
        ->condition('field_domain_value', $currentDomain, '=')
        ->execute()
        ->fetchField();

      //get path
      $path = \Drupal::service('path_alias.manager')->getAliasByPath('/node/'. $nid);

      if (empty($path)) {
        // We have to return a valid path but / won't be routable and config
        // might be broken so stop execution.
        throw new NotFoundHttpException();
      }
      $components = parse_url($path);

      // Remove query string and fragment.
      $path = $components['path'];

      // Merge query parameters from front page configuration value
      // with URL query, so that actual URL takes precedence.
      if (!empty($components['query'])) {
        parse_str($components['query'], $parameters);
        array_replace($parameters, $request->query->all());
        $request->query->replace($parameters);
      }
    }

    return $path;
  }
}

Now when going to a.book.com I see node/1 but when I go to b.book.com I also see node/1. When I clear cache and first go to b.book.com I get node/2 but when going to a.book.com I also get node/2. So this looks like a cache problem.

How can I disable caching of this front page path?

2
  • So you're not using the Domain Access family of modules? Assigning content to different domains, and everything related to that, is their whole deal.
    – sonfd
    Nov 18, 2020 at 16:46
  • No I find the domain module to big and complex to use for my users who need to be able to add their domain themselves. Now the only thing they have to do is fill in the field and they are done.
    – Joost
    Nov 18, 2020 at 16:49

1 Answer 1

4

As discussed here, Hide path on frontpage redirect, you need to make caching of route collections domain-specific. Other than the discussed overriding of the core service, since Drupal 8.8 you can use the service method addExtraCacheKeyPart().

Add this code in a request event subscriber:

/src/EventSubscriber/RouteCacheDomainSubscriber.php

<?php

namespace Drupal\mymodule\EventSubscriber;

use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class RouteCacheDomainSubscriber implements EventSubscriberInterface {
  
  public function onKernelRequest(RequestEvent $event) {
    $routeProvider = \Drupal::service('router.route_provider');
    $domain = \Drupal::request()->getHost();
    $routeProvider->addExtraCacheKeyPart('domain', $domain);
  }

  /**
   * {@inheritDoc}
   */
  public static function getSubscribedEvents() {
    // run before RouterListener (priority 32)
    $events[KernelEvents::REQUEST][] = ['onKernelRequest', 33];
    return $events;
  }

}

mymodule.services.yml

services:
  mymodule.route_cache_domain_subscriber:
    class: Drupal\mymodule\EventSubscriber\RouteCacheDomainSubscriber
    arguments: []
    tags:
      - { name: event_subscriber }

Also, you need to invalidate the cache tag route_match when book nodes are saved. See Is there a way to partially rebuild the router or invalidate a route cache?

8
  • Thanks a lot! Yes I created a new post because this was clearly a different question and I still didn't know what to do. I work now. Only thing I did was changing GetResonseEvent to RequestEvent and changing use Symfony\Component\HttpKernel\Event\GetResponseEvent; to use Symfony\Component\HttpFoundation\Request; to remove the deprecated GetResponseEvent class.
    – Joost
    Nov 19, 2020 at 13:10
  • About your last remark, is this invalidating is necessary for when the path alias changes? And is it possible to solve this by not using the alias path but the default path to the node 'node/1'?
    – Joost
    Nov 19, 2020 at 13:12
  • Sorry, I see now that all content is cached... I'll try and figure it out with the link you gave. Thanks!
    – Joost
    Nov 19, 2020 at 13:20
  • On path alias changes the route_match tag is invalidated automatically. This is core behavior. For this specific custom path processing, which depends on a node field, you need to invalidate the cache tag in custom code when the node is submitted so that when you change the field content the route is matched again. Whether you use node/1 or alias doesn't matter because your path processor runs before aliasing. The only advantage, if you change the alias every time you change the node, you don't need custom code to invalidate the cache tag.
    – 4uk4
    Nov 19, 2020 at 14:05
  • 1
    Concerning the event classes. Change the event class only if you don't need code compatibility with Drupal 8. My code runs in D8 & D9, your code in D9 & D10.
    – 4uk4
    Nov 19, 2020 at 14:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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