From b9d7b665326805e6034fd9d4b7f2a609cc245b44 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 11:05:30 +0000
Subject: [PATCH 1/8] Collect memcache stats only when memcache_admin is
 installed and configure to show stats

---
 memcache.services.yml                         |  2 ++
 memcache_admin/memcache_admin.services.yml    |  1 +
 .../MemcacheAdminSubscriber.php               | 29 ++++++++++++++++++-
 .../src/MemcacheAdminServiceProvider.php      | 24 +++++++++++++++
 src/Driver/DriverBase.php                     | 28 +-----------------
 5 files changed, 56 insertions(+), 28 deletions(-)
 create mode 100644 memcache_admin/src/MemcacheAdminServiceProvider.php

diff --git a/memcache.services.yml b/memcache.services.yml
index d523f10..c4a434c 100644
--- a/memcache.services.yml
+++ b/memcache.services.yml
@@ -1,3 +1,5 @@
+parameters:
+  memcache.stats_collect: false
 services:
   memcache.settings:
     class: Drupal\memcache\MemcacheSettings
diff --git a/memcache_admin/memcache_admin.services.yml b/memcache_admin/memcache_admin.services.yml
index 2f01435..7b8f81b 100644
--- a/memcache_admin/memcache_admin.services.yml
+++ b/memcache_admin/memcache_admin.services.yml
@@ -6,6 +6,7 @@ services:
       - '@config.factory'
       - '@current_user'
       - '@renderer'
+      - '@kernel'
     tags:
       - { name: event_subscriber }
   memcache_stats.memcached:
diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
index 7a01975..5c0266e 100644
--- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
+++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
@@ -3,7 +3,10 @@
 namespace Drupal\memcache_admin\EventSubscriber;
 
 use Drupal\Component\Render\HtmlEscapedText;
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\DrupalKernelInterface;
 use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -47,6 +50,13 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
    */
   protected $renderer;
 
+  /**
+   * The kernel.
+   *
+   * @var \Drupal\Core\DrupalKernelInterface
+   */
+  protected $kernel;
+
   /**
    * MemcacheAdminSubscriber constructor.
    *
@@ -58,12 +68,15 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
    *   The current user.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer service.
+   * @param \Drupal\Core\DrupalKernelInterface $kernel
+   *   The kernel.
    */
-  public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer) {
+  public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) {
     $this->memcacheFactory = $memcache_factory;
     $this->configFactory = $config_factory;
     $this->currentUser = $current_user;
     $this->renderer = $renderer;
+    $this->kernel = $kernel;
   }
 
   /**
@@ -72,6 +85,7 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
   public static function getSubscribedEvents(): array {
     $events = [];
     $events[KernelEvents::RESPONSE][] = ['displayStatistics'];
+    $events[ConfigEvents::SAVE][] = ['onConfigSave', 0];
     return $events;
   }
 
@@ -166,6 +180,19 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
     }
   }
 
+  /**
+   * Causes the container to be rebuilt on the next request, if necessary.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The configuration event.
+   */
+  public function onConfigSave(ConfigCrudEvent $event) {
+    $saved_config = $event->getConfig();
+    if ($saved_config->getName() == 'memcache_admin.settings' && $event->isChanged('show_memcache_statistics')) {
+      $this->kernel->invalidateContainer();
+    }
+  }
+
   /**
    * Helper function. Calculate a percentage.
    */
diff --git a/memcache_admin/src/MemcacheAdminServiceProvider.php b/memcache_admin/src/MemcacheAdminServiceProvider.php
new file mode 100644
index 0000000..bef80ab
--- /dev/null
+++ b/memcache_admin/src/MemcacheAdminServiceProvider.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\memcache_admin;
+
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceModifierInterface;
+
+/**
+ * Enables memcache stats collection based on configuration.
+ */
+class MemcacheAdminServiceProvider implements ServiceModifierInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    $config_storage = BootstrapConfigStorageFactory::get();
+    $config = $config_storage->read('memcache_admin.settings');
+    // Determine whether stats should be collected using configuration.
+    $container->setParameter('memcache.stats_collect', $config['show_memcache_statistics'] ?? FALSE);
+  }
+
+}
diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php
index 7e9b6d0..de6e540 100644
--- a/src/Driver/DriverBase.php
+++ b/src/Driver/DriverBase.php
@@ -302,33 +302,7 @@ abstract class DriverBase implements DrupalMemcacheInterface {
    * Helper function to initialize the stats for a memcache operation.
    */
   protected function statsInit() {
-    static $drupal_static_fast;
-
-    if (!isset($drupal_static_fast)) {
-      // @phpcs:ignore
-      $drupal_static_fast = &drupal_static(__FUNCTION__, ['variable_checked' => NULL, 'user_access_checked' => NULL]);
-    }
-    $variable_checked = &$drupal_static_fast['variable_checked'];
-    $user_access_checked = &$drupal_static_fast['user_access_checked'];
-
-    // Confirm DRUPAL_BOOTSTRAP_VARIABLES has been reached. We don't use
-    // drupal_get_bootstrap_phase() as it's buggy. We can use variable_get()
-    // here because _drupal_bootstrap_variables() includes module.inc
-    // immediately after it calls variable_initialize().
-    // @codingStandardsIgnoreStart
-    // if (!isset($variable_checked) && function_exists('module_list')) {
-    //   $variable_checked = variable_get('show_memcache_statistics', FALSE);
-    // }
-    // If statistics are enabled we need to check user access.
-    // if (!empty($variable_checked) && !isset($user_access_checked) && !empty($GLOBALS['user']) && function_exists('user_access')) {
-    //   // Statistics are enabled and the $user object has been populated, so check
-    //   // that the user has access to view them.
-    //   $user_access_checked = user_access('access memcache statistics');
-    // }
-    // @codingStandardsIgnoreEnd
-    // Return whether or not statistics are enabled and the user can access
-    // them.
-    if ((!isset($variable_checked) || $variable_checked) && (!isset($user_access_checked) || $user_access_checked)) {
+    if (\Drupal::getContainer()->getParameter('memcache.stats_collect')) {
       Timer::start('dmemcache');
       return TRUE;
     }
-- 
GitLab


From 55b80f92f8093755250ae71a5bf265dba050663f Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 11:57:01 +0000
Subject: [PATCH 2/8] Disable stat collection if user does not have access

---
 memcache.services.yml                         |   2 +-
 memcache_admin/memcache_admin.services.yml    |   2 +-
 .../MemcacheAdminSubscriber.php               | 173 ++++++++++--------
 src/Driver/DriverBase.php                     |  29 ++-
 src/Driver/MemcacheDriverFactory.php          |  16 +-
 5 files changed, 132 insertions(+), 90 deletions(-)

diff --git a/memcache.services.yml b/memcache.services.yml
index c4a434c..2cb1ef7 100644
--- a/memcache.services.yml
+++ b/memcache.services.yml
@@ -6,7 +6,7 @@ services:
     arguments: ['@settings']
   memcache.factory:
     class: Drupal\memcache\Driver\MemcacheDriverFactory
-    arguments: ['@memcache.settings']
+    arguments: ['@memcache.settings', '%memcache.stats_collect%']
   memcache.timestamp.invalidator.bin:
     class: Drupal\memcache\Invalidator\MemcacheTimestampInvalidator
     # Override this service and adjust tolerance if necessary.
diff --git a/memcache_admin/memcache_admin.services.yml b/memcache_admin/memcache_admin.services.yml
index 7b8f81b..ce5f7de 100644
--- a/memcache_admin/memcache_admin.services.yml
+++ b/memcache_admin/memcache_admin.services.yml
@@ -3,7 +3,7 @@ services:
     class: Drupal\memcache_admin\EventSubscriber\MemcacheAdminSubscriber
     arguments:
       - '@memcache.factory'
-      - '@config.factory'
+      - '%memcache.stats_collect%'
       - '@current_user'
       - '@renderer'
       - '@kernel'
diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
index 5c0266e..250f21c 100644
--- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
+++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
@@ -13,6 +13,7 @@ use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\memcache\Driver\MemcacheDriverFactory;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
 use Symfony\Component\HttpKernel\Event\ResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 
@@ -30,11 +31,11 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
   protected $memcacheFactory;
 
   /**
-   * The config factory service.
+   * Indicates if memcache is collecting stats.
    *
-   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   * @var bool
    */
-  protected $configFactory;
+  protected bool $statsEnabled;
 
   /**
    * The current user.
@@ -62,8 +63,8 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
    *
    * @param \Drupal\memcache\Driver\MemcacheDriverFactory $memcache_factory
    *   The memcache factory service.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory service.
+   * @param bool $stats_enabled
+   *   The 'memcache.stats_collect' container parameter.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
    * @param \Drupal\Core\Render\RendererInterface $renderer
@@ -71,9 +72,9 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The kernel.
    */
-  public function __construct(MemcacheDriverFactory $memcache_factory, ConfigFactoryInterface $config_factory, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) {
+  public function __construct(MemcacheDriverFactory $memcache_factory, bool $stats_enabled, AccountInterface $current_user, RendererInterface $renderer, DrupalKernelInterface $kernel) {
     $this->memcacheFactory = $memcache_factory;
-    $this->configFactory = $config_factory;
+    $this->statsEnabled = $stats_enabled;
     $this->currentUser = $current_user;
     $this->renderer = $renderer;
     $this->kernel = $kernel;
@@ -84,96 +85,108 @@ class MemcacheAdminSubscriber implements EventSubscriberInterface {
    */
   public static function getSubscribedEvents(): array {
     $events = [];
+    // Needs to come after user authentication.
+    $events[KernelEvents::REQUEST][] = ['disableStatisticsIfNoPermission', 299];
     $events[KernelEvents::RESPONSE][] = ['displayStatistics'];
     $events[ConfigEvents::SAVE][] = ['onConfigSave', 0];
     return $events;
   }
 
+  /**
+   * Disables statistics if user does not have permission.
+   */
+  public function disableStatisticsIfNoPermission(RequestEvent $event) {
+    if (!$this->statsEnabled) {
+      return;
+    }
+    if (!$this->currentUser->hasPermission('access memcache statistics')) {
+      $memcache = $this->memcacheFactory->get(NULL, TRUE);
+      $memcache->disableStats();
+    }
+  }
+
   /**
    * Display statistics on page.
    */
   public function displayStatistics(ResponseEvent $event) {
-    $user = $this->currentUser;
-    if ($user->id() == 0) {
-      // Suppress for the above criteria.
+    if (!$this->statsEnabled) {
+      return;
     }
-    else {
-      $response = $event->getResponse();
-
-      // Don't call theme() during shutdown if the registry has been rebuilt
-      // (such as when enabling/disabling modules on admin/build/modules) as
-      // things break.
-      // Instead, simply exit without displaying admin statistics for this page
-      // load.  See http://drupal.org/node/616282 for discussion.
-      // @todo make sure this is not still a requirement.
-      // @codingStandardsIgnoreStart
-      // if (!function_exists('theme_get_registry') || !theme_get_registry()) {
-      //   return;
-      // }.
-      // @codingStandardsIgnoreEnd
-      // Try not to break non-HTML pages.
-      if ($response instanceof HtmlResponse) {
-
-        // This should only apply to page content.
-        if (stripos((string) $response->headers->get('content-type'), 'text/html') !== FALSE) {
-          $show_stats = $this->configFactory->get('memcache_admin.settings')->get('show_memcache_statistics');
-          if ($show_stats && $user->hasPermission('access memcache statistics')) {
-            $output = '';
-
-            $memcache = $this->memcacheFactory->get(NULL, TRUE);
-            $memcache_stats = $memcache->requestStats();
-            if (!empty($memcache_stats['ops'])) {
-              foreach ($memcache_stats['ops'] as $row => $stats) {
-                $memcache_stats['ops'][$row][0] = new HtmlEscapedText($stats[0]);
-                $memcache_stats['ops'][$row][1] = number_format($stats[1], 2);
-                $hits = number_format($this->statsPercent($stats[2], $stats[3]), 1);
-                $misses = number_format($this->statsPercent($stats[3], $stats[2]), 1);
-                $memcache_stats['ops'][$row][2] = number_format($stats[2]) . " ($hits%)";
-                $memcache_stats['ops'][$row][3] = number_format($stats[3]) . " ($misses%)";
-              }
 
-              $build = [
-                '#theme' => 'table',
-                '#header' => [
-                  $this->t('operation'),
-                  $this->t('total ms'),
-                  $this->t('total hits'),
-                  $this->t('total misses'),
-                ],
-                '#rows' => $memcache_stats['ops'],
-              ];
-              $output .= $this->renderer->renderRoot($build);
+    $response = $event->getResponse();
+
+    // Don't call theme() during shutdown if the registry has been rebuilt
+    // (such as when enabling/disabling modules on admin/build/modules) as
+    // things break.
+    // Instead, simply exit without displaying admin statistics for this page
+    // load.  See http://drupal.org/node/616282 for discussion.
+    // @todo make sure this is not still a requirement.
+    // @codingStandardsIgnoreStart
+    // if (!function_exists('theme_get_registry') || !theme_get_registry()) {
+    //   return;
+    // }.
+    // @codingStandardsIgnoreEnd
+    // Try not to break non-HTML pages.
+    if ($response instanceof HtmlResponse) {
+
+      // This should only apply to page content.
+      if (stripos((string) $response->headers->get('content-type'), 'text/html') !== FALSE) {
+        if ($this->currentUser->hasPermission('access memcache statistics')) {
+          $output = '';
+
+          $memcache = $this->memcacheFactory->get(NULL, TRUE);
+          $memcache_stats = $memcache->requestStats();
+          if (!empty($memcache_stats['ops'])) {
+            foreach ($memcache_stats['ops'] as $row => $stats) {
+              $memcache_stats['ops'][$row][0] = new HtmlEscapedText($stats[0]);
+              $memcache_stats['ops'][$row][1] = number_format($stats[1], 2);
+              $hits = number_format($this->statsPercent($stats[2], $stats[3]), 1);
+              $misses = number_format($this->statsPercent($stats[3], $stats[2]), 1);
+              $memcache_stats['ops'][$row][2] = number_format($stats[2]) . " ($hits%)";
+              $memcache_stats['ops'][$row][3] = number_format($stats[3]) . " ($misses%)";
             }
 
-            if (!empty($memcache_stats['all'])) {
-              $build = [
-                '#type'  => 'table',
-                '#header' => [
-                  $this->t('ms'),
-                  $this->t('operation'),
-                  $this->t('bin'),
-                  $this->t('key'),
-                  $this->t('status'),
-                ],
+            $build = [
+              '#theme' => 'table',
+              '#header' => [
+                $this->t('operation'),
+                $this->t('total ms'),
+                $this->t('total hits'),
+                $this->t('total misses'),
+              ],
+              '#rows' => $memcache_stats['ops'],
+            ];
+            $output .= $this->renderer->renderRoot($build);
+          }
+
+          if (!empty($memcache_stats['all'])) {
+            $build = [
+              '#type'  => 'table',
+              '#header' => [
+                $this->t('ms'),
+                $this->t('operation'),
+                $this->t('bin'),
+                $this->t('key'),
+                $this->t('status'),
+              ],
+            ];
+            foreach ($memcache_stats['all'] as $row => $stats) {
+              $build[$row]['ms'] = ['#plain_text' => $stats[0]];
+              $build[$row]['operation'] = ['#plain_text' => $stats[1]];
+              $build[$row]['bin'] = ['#plain_text' => $stats[2]];
+              $build[$row]['key'] = [
+                '#separator' => ' | ',
               ];
-              foreach ($memcache_stats['all'] as $row => $stats) {
-                $build[$row]['ms'] = ['#plain_text' => $stats[0]];
-                $build[$row]['operation'] = ['#plain_text' => $stats[1]];
-                $build[$row]['bin'] = ['#plain_text' => $stats[2]];
-                $build[$row]['key'] = [
-                  '#separator' => ' | ',
-                ];
-                foreach (explode('\n', $stats[3]) as $akey) {
-                  $build[$row]['key']['child'][]['#plain_text'] = $akey;
-                }
-                $build[$row]['status'] = ['#plain_text' => $stats[4]];
+              foreach (explode('\n', $stats[3]) as $akey) {
+                $build[$row]['key']['child'][]['#plain_text'] = $akey;
               }
-              $output .= $this->renderer->renderRoot($build);
+              $build[$row]['status'] = ['#plain_text' => $stats[4]];
             }
+            $output .= $this->renderer->renderRoot($build);
+          }
 
-            if (!empty($output)) {
-              $response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>');
-            }
+          if (!empty($output)) {
+            $response->setContent($response->getContent() . '<div id="memcache-devel"><h2>' . $this->t('Memcache statistics') . '</h2>' . $output . '</div>');
           }
         }
       }
diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php
index de6e540..eb06a04 100644
--- a/src/Driver/DriverBase.php
+++ b/src/Driver/DriverBase.php
@@ -53,6 +53,13 @@ abstract class DriverBase implements DrupalMemcacheInterface {
     'ops' => [],
   ];
 
+  /**
+   * Flag to indicate if stats collection has been enabled.
+   *
+   * @var bool
+   */
+  private static bool $statsEnabled;
+
   /**
    * Constructs a DriverBase object.
    *
@@ -62,8 +69,10 @@ abstract class DriverBase implements DrupalMemcacheInterface {
    *   An existing memcache connection object.
    * @param string $bin
    *   The class instance specific cache bin to use.
+   * @param bool $statsEnabled
+   *   Flag to indicate if stats collection has been enabled.
    */
-  public function __construct(MemcacheSettings $settings, $memcache, $bin = NULL) {
+  public function __construct(MemcacheSettings $settings, $memcache, $bin = NULL, bool $statsEnabled = FALSE) {
     $this->settings = $settings;
     $this->memcache = $memcache;
     $this->hashAlgorithm = $this->settings->get('key_hash_algorithm', 'sha1');
@@ -74,6 +83,7 @@ abstract class DriverBase implements DrupalMemcacheInterface {
     if ($bin) {
       $this->prefix .= $bin . ':';
     }
+    self::$statsEnabled = $statsEnabled;
   }
 
   /**
@@ -242,6 +252,16 @@ abstract class DriverBase implements DrupalMemcacheInterface {
     return $stats;
   }
 
+  public function disableStats() {
+    // Clear up.
+    self::$stats = [
+      'all' => [],
+      'ops' => [],
+    ];
+    // Stop recording.
+    self::$statsEnabled = FALSE;
+  }
+
   /**
    * Helper function to get the bins.
    */
@@ -302,13 +322,12 @@ abstract class DriverBase implements DrupalMemcacheInterface {
    * Helper function to initialize the stats for a memcache operation.
    */
   protected function statsInit() {
-    if (\Drupal::getContainer()->getParameter('memcache.stats_collect')) {
+    if (self::$statsEnabled) {
       Timer::start('dmemcache');
       return TRUE;
     }
-    else {
-      return FALSE;
-    }
+
+    return FALSE;
   }
 
   /**
diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php
index 3730376..d7bcf05 100644
--- a/src/Driver/MemcacheDriverFactory.php
+++ b/src/Driver/MemcacheDriverFactory.php
@@ -68,14 +68,24 @@ class MemcacheDriverFactory {
    */
   protected $failedConnectionCache = [];
 
+  /**
+   * Flag to indicate if stats collection has been enabled.
+   *
+   * @var bool
+   */
+  private bool $statsEnabled;
+
   /**
    * Constructs a MemcacheDriverFactory object.
    *
    * @param \Drupal\memcache\MemcacheSettings $settings
    *   The settings object.
-   */
-  public function __construct(MemcacheSettings $settings) {
+   * @param bool $statsEnabled
+   * *   Flag to indicate if stats collection has been enabled.
+ */
+  public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) {
     $this->settings = $settings;
+    $this->statsEnabled = $statsEnabled;
 
     $this->initialize();
   }
@@ -148,7 +158,7 @@ class MemcacheDriverFactory {
       }
     }
 
-    return empty($this->connections[$bin]) ? FALSE : new $this->driverClass($this->settings, $this->connections[$bin]->getMemcache(), $bin);
+    return empty($this->connections[$bin]) ? FALSE : new $this->driverClass($this->settings, $this->connections[$bin]->getMemcache(), $bin, $this->statsEnabled);
   }
 
   /**
-- 
GitLab


From a7fa783afbfca6ae22a9fb6b784fb01ec7200c95 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 12:21:52 +0000
Subject: [PATCH 3/8] PHPCS

---
 memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php | 1 -
 src/Driver/DriverBase.php                                      | 3 +++
 src/Driver/MemcacheDriverFactory.php                           | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
index 250f21c..8aa05b7 100644
--- a/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
+++ b/memcache_admin/src/EventSubscriber/MemcacheAdminSubscriber.php
@@ -5,7 +5,6 @@ namespace Drupal\memcache_admin\EventSubscriber;
 use Drupal\Component\Render\HtmlEscapedText;
 use Drupal\Core\Config\ConfigCrudEvent;
 use Drupal\Core\Config\ConfigEvents;
-use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\DrupalKernelInterface;
 use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Render\RendererInterface;
diff --git a/src/Driver/DriverBase.php b/src/Driver/DriverBase.php
index eb06a04..bac0d34 100644
--- a/src/Driver/DriverBase.php
+++ b/src/Driver/DriverBase.php
@@ -252,6 +252,9 @@ abstract class DriverBase implements DrupalMemcacheInterface {
     return $stats;
   }
 
+  /**
+   * Disables stats collection.
+   */
   public function disableStats() {
     // Clear up.
     self::$stats = [
diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php
index d7bcf05..6bd36c5 100644
--- a/src/Driver/MemcacheDriverFactory.php
+++ b/src/Driver/MemcacheDriverFactory.php
@@ -81,7 +81,7 @@ class MemcacheDriverFactory {
    * @param \Drupal\memcache\MemcacheSettings $settings
    *   The settings object.
    * @param bool $statsEnabled
-   * *   Flag to indicate if stats collection has been enabled.
+   *   Flag to indicate if stats collection has been enabled.
  */
   public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) {
     $this->settings = $settings;
-- 
GitLab


From c0cf15e3ca89937a21217b41d1c0404dfd602dc0 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 14:18:15 +0000
Subject: [PATCH 4/8] Add a test

---
 .../src/Functional/MemcacheAdminStatsTest.php | 98 +++++++++++++++++++
 1 file changed, 98 insertions(+)
 create mode 100644 memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php

diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
new file mode 100644
index 0000000..5bb5ecc
--- /dev/null
+++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\Tests\memcache\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\user\Entity\Role;
+
+/**
+ * Tests displaying memcache statistics.
+ *
+ * @group memcache
+ */
+class MemcacheAdminStatsTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['memcache', 'memcache_admin'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp(): void {
+    parent::setUp();
+    $host = getenv('MEMCACHED_HOST') ?: '127.0.0.1:11211';
+    $settings['settings']['memcache'] = (object) [
+      'value' => [
+        'servers' => [$host => 'default'],
+        'bin' => ['default' => 'default'],
+        'key_prefix' => $this->databasePrefix,
+      ],
+      'required' => TRUE,
+    ];
+    $settings['settings']['cache']['default'] = (object) [
+      'value' => 'cache.backend.memcache',
+      'required' => TRUE,
+    ];
+
+    $settings['settings']['hash_salt'] = (object) [
+      'value' => $this->randomMachineName(),
+      'required' => TRUE,
+    ];
+
+    $this->writeSettings($settings);
+    $this->rebuildAll();
+  }
+
+  /**
+   * Tests displaying memcache statistics.
+   */
+  public function testStats(): void {
+    // By default, stats are not collected.
+    $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect'));
+
+    $this->drupalLogin($this->createUser(['administer site configuration']));
+    $this->drupalGet('admin/config/system/memcache');
+    $this->assertSession()->fieldExists('show_memcache_statistics')->check();
+    $this->assertSession()->buttonExists('Save configuration')->click();
+
+    // The user does not have the permission so should not see any stats.
+    $this->drupalGet('/');
+    $this->assertSession()->pageTextNotContains('Memcache statistics');
+
+    // A user with the permission can see stats.
+    $this->drupalLogin($this->createUser(['access memcache statistics']));
+    $this->drupalGet('/');
+    $this->assertSession()->pageTextContains('Memcache statistics');
+
+    // The anonymous user can not see stats unless they have the permission.
+    $this->drupalLogout();
+    $this->drupalGet('/');
+    $this->assertSession()->pageTextNotContains('Memcache statistics');
+
+    // Ensure you can set up anonymous users to see memcache stats so for
+    // debugging purposes.
+    Role::load(Role::ANONYMOUS_ID)->grantPermission('access memcache statistics')->save();
+    $this->drupalGet('/');
+    $this->assertSession()->pageTextContains('Memcache statistics');
+
+    // Rebuild the container in the test so we can assert against it. The form
+    // save above will have triggered a rebuild in the site-under-test but we
+    // wait till now so the test confirms this has worked.
+    $this->rebuildContainer();
+    $this->assertTrue(\Drupal::getContainer()->getParameter('memcache.stats_collect'));
+
+    \Drupal::service('module_installer')->uninstall(['memcache_admin']);
+    $this->rebuildAll();
+    $this->drupalGet('/');
+    $this->assertSession()->pageTextNotContains('Memcache statistics');
+    $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect'));
+  }
+
+}
-- 
GitLab


From 01706230292ecc40509267faf7d298bca4481a1a Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 14:31:28 +0000
Subject: [PATCH 5/8] Add empty update to indicate container needs a rebuild

---
 memcache.post_update.php | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/memcache.post_update.php b/memcache.post_update.php
index 77cc0d8..98c4209 100644
--- a/memcache.post_update.php
+++ b/memcache.post_update.php
@@ -13,3 +13,10 @@ function memcache_post_update_add_datetime() {
   $kernel = \Drupal::service('kernel');
   $kernel->invalidateContainer();
 }
+
+/**
+ * Rebuild the service container to add the 'memcache.stats_collect' container parameter.
+ */
+function memcache_post_update_add_container_parameter() {
+  // Intentionally empty.
+}
-- 
GitLab


From c94dafaa7578dd6bfb0e403ea7d1604ae42ebd28 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 14:32:45 +0000
Subject: [PATCH 6/8] Fixes

---
 memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php | 2 +-
 src/Driver/MemcacheDriverFactory.php                           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
index 5bb5ecc..7f0fe59 100644
--- a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
+++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\Tests\memcache\Functional;
+namespace Drupal\Tests\memcache_admin\Functional;
 
 use Drupal\Tests\BrowserTestBase;
 use Drupal\user\Entity\Role;
diff --git a/src/Driver/MemcacheDriverFactory.php b/src/Driver/MemcacheDriverFactory.php
index 6bd36c5..d395841 100644
--- a/src/Driver/MemcacheDriverFactory.php
+++ b/src/Driver/MemcacheDriverFactory.php
@@ -82,7 +82,7 @@ class MemcacheDriverFactory {
    *   The settings object.
    * @param bool $statsEnabled
    *   Flag to indicate if stats collection has been enabled.
- */
+   */
   public function __construct(MemcacheSettings $settings, bool $statsEnabled = FALSE) {
     $this->settings = $settings;
     $this->statsEnabled = $statsEnabled;
-- 
GitLab


From 401ce781501e3bfead5ec46cc072c585cc14db76 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 14:36:51 +0000
Subject: [PATCH 7/8] Pointless and in this case incorrect PHPCS rule but
 whatevs

---
 memcache.post_update.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/memcache.post_update.php b/memcache.post_update.php
index 98c4209..59daa08 100644
--- a/memcache.post_update.php
+++ b/memcache.post_update.php
@@ -15,7 +15,7 @@ function memcache_post_update_add_datetime() {
 }
 
 /**
- * Rebuild the service container to add the 'memcache.stats_collect' container parameter.
+ * Rebuild container to add the 'memcache.stats_collect' container parameter.
  */
 function memcache_post_update_add_container_parameter() {
   // Intentionally empty.
-- 
GitLab


From ec9f95261dd7af6da6d71122c00a93f52b8d5a6e Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 5 Mar 2025 14:40:15 +0000
Subject: [PATCH 8/8] Fix test on gitlab

---
 .../tests/src/Functional/MemcacheAdminStatsTest.php  | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
index 7f0fe59..84ed87d 100644
--- a/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
+++ b/memcache_admin/tests/src/Functional/MemcacheAdminStatsTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\memcache_admin\Functional;
 
+use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\user\Entity\Role;
 
@@ -54,6 +55,7 @@ class MemcacheAdminStatsTest extends BrowserTestBase {
    * Tests displaying memcache statistics.
    */
   public function testStats(): void {
+    $front = Url::fromRoute('<front>');
     // By default, stats are not collected.
     $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect'));
 
@@ -63,23 +65,23 @@ class MemcacheAdminStatsTest extends BrowserTestBase {
     $this->assertSession()->buttonExists('Save configuration')->click();
 
     // The user does not have the permission so should not see any stats.
-    $this->drupalGet('/');
+    $this->drupalGet($front);
     $this->assertSession()->pageTextNotContains('Memcache statistics');
 
     // A user with the permission can see stats.
     $this->drupalLogin($this->createUser(['access memcache statistics']));
-    $this->drupalGet('/');
+    $this->drupalGet($front);
     $this->assertSession()->pageTextContains('Memcache statistics');
 
     // The anonymous user can not see stats unless they have the permission.
     $this->drupalLogout();
-    $this->drupalGet('/');
+    $this->drupalGet($front);
     $this->assertSession()->pageTextNotContains('Memcache statistics');
 
     // Ensure you can set up anonymous users to see memcache stats so for
     // debugging purposes.
     Role::load(Role::ANONYMOUS_ID)->grantPermission('access memcache statistics')->save();
-    $this->drupalGet('/');
+    $this->drupalGet($front);
     $this->assertSession()->pageTextContains('Memcache statistics');
 
     // Rebuild the container in the test so we can assert against it. The form
@@ -90,7 +92,7 @@ class MemcacheAdminStatsTest extends BrowserTestBase {
 
     \Drupal::service('module_installer')->uninstall(['memcache_admin']);
     $this->rebuildAll();
-    $this->drupalGet('/');
+    $this->drupalGet($front);
     $this->assertSession()->pageTextNotContains('Memcache statistics');
     $this->assertFalse(\Drupal::getContainer()->getParameter('memcache.stats_collect'));
   }
-- 
GitLab