0

In Drupal 7 hook_exit is implemented in the Statistics module to gather statistics for page accesses. This change record suggest the equivalent in Drupal 8 is to listen for kernel.terminate events. Here's my code:

/mymodule/mymodule.services.yml

services:
  mymodule.page_exit:
    class: Drupal\mymodule\EventSubscriber\PageExit
    tags:
      - {name: event_subscriber}

/mymodule/src/Event/Subscriber/PageExit.php

namespace Drupal\mymodule\EventSubscriber;

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

class PageExit implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  static function getSubscribedEvents() {
    $events[KernelEvents::TERMINATE][] = array('logAccess');
    return $events;
  }

  /**
   * Call this method whenever the KernelEvents::TERMINATE event is dispatched.
   */
  public function logAccess(PostResponseEvent $event) {

    // $access_info = $event->getRequest()

    \Drupal::database()->insert('mymodule_table')
      ->fields(array(
        'hostname' => '?',
        'referrer_url' => '?',
        'timestamp' => REQUEST_TIME,
      ))
      ->execute();
  }
}

Currently, for a single page request, the method logAccess gets called five times. I'm interested in logging the hostname and referrer url of my page visitor, just once.

In essence, I'm trying to port some of this this Drupal 7 code from statistics_exit in statistics.module:

drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);

// For anonymous users unicode.inc will not have been loaded.
include_once DRUPAL_ROOT . '/includes/unicode.inc';
// Log this page access.
db_insert('accesslog')
  ->fields(array(
    // 'title' => truncate_utf8(strip_tags(drupal_get_title()), 255),
    // 'path' => truncate_utf8($_GET['q'], 255),
    'url' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
    'hostname' => ip_address(),
    // 'uid' => $user->uid,
    // 'sid' => session_id(),
    // 'timer' => (int) timer_read('page'),
    'timestamp' => REQUEST_TIME,
  ))
  ->execute();

Any ideas?

5
  • 1
    I think the reason for getting 5 entries is because there are requests on the background(js, css or most likely image style generated images and they go through the index file causing subrequests). So you have to check the type of the response and/or route and do a log afterwards. For example this is my counter gist.github.com/ivanjaros/211e499b76385565c7b652741e669565 Commented Jul 19, 2017 at 7:12
  • statistics module in 8.x actually uses a JS file that triggers a POST request to the server, that is the only thing that also works with external caches like Varnish/Fastly and so on. Commented Jul 19, 2017 at 22:37
  • That same mechanism is used in 7.x for counting nodes, but not for logging access statistics, right? In 7.x they are logged within an implementation of hook_exit (line 82 of statistics.module). Is using hook_exit for this purpose in 7.x unreliable? Commented Jul 20, 2017 at 8:51
  • Did you even had a look at the suggestion below? It seems like you are still trying to solve the same problem => drupal.stackexchange.com/questions/243308/… Commented Aug 11, 2017 at 23:18
  • Thanks - I did see your reply. I decided not to use an event subscriber in the end because of the comment by @Berdir. My code could be used on an environment that uses external caching, so I decided it wasn't the way to go. Commented Aug 12, 2017 at 10:36

1 Answer 1

0

The comments you got are golden, my answer is along the lines of what @IvanJaros suggested, but follows a simpler approach. And take care to check the case of external caching (if any is in play).

The following should make your current approach work.

public function logAccess(PostResponseEvent $event) {

  $request_format = $event->getRequest()->getRequestFormat();

  if ($request_format === 'html') {
    // \Drupal::database()->
  }
  else {
    // Do other stuff.
  }
  ...

This snippet tries to detect requests with the html format (i.e. page requests) and will ignore other ones, like e.g. application/json.

Good luck!

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.