1

In the past there were some hooks, such as described here: How to run check on each page load?

to execute arbitrary code on each page load.

Now this has been replaced with an event based approach. I wrote this to check it out:

class MyModuleEventSubscriber implements EventSubscriberInterface {
    public function onLoad(GetResponseEvent $event) {
        \Drupal::logger('my_module')->info('test');
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() {
        $events[KernelEvents::REQUEST][] = ['onLoad'];

        return $events;
    }
}

However, it seems like there is any number of requests on each page load—it actually captures each request, and in my case there are five (5). My intended code, however, has to call an outside API, and I'd like to avoid unnecessary calls.

Is there an event that is guaranteed to run just once on each page load, without saving a state on my side (that seems quite complicated for this problem)?

1
  • 2
    A page load can have multiple requests, for example ajax requests for quickedit. If you have five in one page load, you are probably logged in. Then you can control the number of requests for the same page with the help of a session value. For anonymous requests this is very difficult to control because the page can be cached in many places and it depends on your specific hosting environment how you can take control of all these caches including the browser cache. In this case an ajax request from the client on each page load triggering the acton on the server is probably the easiest solution. – 4k4 Dec 5 '19 at 14:26
0

You can see an article on the subject here without using Events

https://chromatichq.com/blog/replacing-hookboot-and-hookinit-functionality-drupal-8

Possible that it will meet your needs.

Replacing hook_boot and hook_init Functionality in Drupal 8

The change notice page states:

Using StackMiddleware

This comment on the hook_boot change notice page provides an example of using StackMiddleware. It provides 95% of the functionality needed to run logic on cached pages by utilizing a tagged service with the http_middleware tag. Since the new class is a service, it will have full access to other core and contrib services, allowing for much greater functionality. The example shows the following for a module’s *.services.yml file:

services:   http_middleware.mymodule:
  class: Drupal\mymodule\StackMiddleware\MyModule
  tags:
    - { name: http_middleware, priority: 180, responder: true }

This is a pretty standard service definition, but note the items added to the tags property that register our service with the http_middleware tag and also set a priority. In order to bypass the page cache, understanding the page_cache.services.yml file is helpful. There, a similar definition can be found, but with a higher priority value.

services:   http_middleware.page_cache:
    class: Drupal\page_cache\StackMiddleware\PageCache
    arguments: ['@cache.render', '@page_cache_request_policy', '@page_cache_response_policy']
    tags:
      - { name: http_middleware, priority: 200, responder: true }

Higher priority services are run first. So to trigger logic before the page cache module takes over the request, a priority greater than 200 is needed.

services:   http_middleware.mymodule:
    class: Drupal\mymodule\StackMiddleware\MyModule
    tags:
      - { name: http_middleware, priority: 210, responder: true }

With this change in the services files, and proper setup of the service as described by the comment, the http_middleware.mymodule service should now be called on every page load, even on fully cached pages.

namespace Drupal\example\StackMiddleware;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

/**
 * Performs a custom task.
 */
class ExampleStackMiddleware implements HttpKernelInterface {

  /**
   * The wrapped HTTP kernel.
   *
   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
   */
  protected $httpKernel;

  /**
   * Creates a HTTP middleware handler.
   *
   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
   *   The HTTP kernel.
   */
  public function __construct(HttpKernelInterface $kernel) {
    $this->httpKernel = $kernel;
  }

  /**
   * {@inheritdoc}
   */
  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
    // Custom logic goes here.

    return $this->httpKernel->handle($request, $type, $catch);
  }
}

Verifying the Results

A quick and easy way to test all of this is to simply add \Drupal::logger('test')->notice(‘not cached’); into the functions triggered by each of the approaches above. Ensure that the Drupal cache is enabled, and simply refresh a page while watching your log (drush ws --tail). Then verify the logic is being called as expected.

3
  • Thanks. I actually saw that page, and just edited my question, clarifying that caching is not the main issue. The question is, is there a reliable way to run code exactly one time on each page load? – Ynhockey Dec 5 '19 at 13:51
  • While this link may answer the question it's necessary to include essential parts of it in your answer. What's your answer worth otherwise if the link goes 404 tomorrow? – leymannx Dec 5 '19 at 13:55
  • You're right, I edited the post accordingly. That said, I am discovering the practices of DrupalExchange. In my humble opinion, a 404 error remains a risk when operating on the Internet and it seems to me that I quite often cross many referenced links without adding a whole slice of bread, it is a common practice on the Internet, is it not? – Condutiarii Dec 5 '19 at 14:12

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.