From ff76379800e6ca6ad2be6d350c3081b9e216de2a Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Sat, 19 Nov 2022 11:42:04 +0100
Subject: [PATCH] [TASK] Modernize ext:adminpanel
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Use more DI
* Avoid MainController being a singleton, have it DI shared
* Avoid static state in InMemoryLogWriter
* Add various type hints
* Streamline annotations
* Avoid a useless exception class
* Use inject* method for DI in an abstract class to avoid
  polluting __construct() for consumers.

Resolves: #99137
Releases: main
Change-Id: I15046da24581fccc1524af6ab3320de6c6880c78
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/76704
Tested-by: Nikita Hovratov <nikita.h@live.de>
Reviewed-by: Nikita Hovratov <nikita.h@live.de>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 Build/phpstan/phpstan-baseline.neon           |  5 ---
 .../Classes/Controller/AjaxController.php     | 13 ++----
 .../Classes/Controller/MainController.php     | 32 +++++----------
 .../InvalidConfigurationException.php         | 25 ------------
 .../Classes/Log/DoctrineSqlLogger.php         | 33 ++++-----------
 .../Classes/Log/InMemoryLogWriter.php         | 40 ++++++++++---------
 .../Classes/Middleware/SqlLogging.php         | 11 ++---
 .../Classes/ModuleApi/AbstractModule.php      | 34 +++++-----------
 .../Classes/ModuleApi/AbstractSubModule.php   |  4 +-
 .../Classes/ModuleApi/ModuleData.php          |  2 +-
 .../ModuleApi/ModuleDataStorageCollection.php |  5 +--
 .../adminpanel/Classes/Modules/Debug/Log.php  | 11 +++--
 .../Classes/Modules/Debug/PageTitle.php       |  5 ++-
 .../Classes/Modules/DebugModule.php           |  4 +-
 .../Classes/Modules/PreviewModule.php         |  3 +-
 .../Modules/TsDebug/TypoScriptWaterfall.php   |  8 ++--
 .../Classes/Service/ConfigurationService.php  |  5 +--
 .../Classes/Service/ModuleLoader.php          |  5 +--
 .../adminpanel/Configuration/Services.yaml    | 19 +++++++++
 .../Tests/Unit/Fixtures/MainModuleFixture.php | 23 -----------
 .../Tests/Unit/Fixtures/SubModuleFixture.php  | 16 --------
 .../Middleware/AdminPanelInitiatorTest.php    | 14 +++----
 .../Tests/Unit/Modules/PreviewModuleTest.php  |  5 +--
 .../Unit/Service/ConfigurationServiceTest.php |  5 ---
 .../Tests/Unit/Service/ModuleLoaderTest.php   |  9 ++---
 .../Tests/Unit/Utility/StateUtilityTest.php   |  8 ----
 26 files changed, 110 insertions(+), 234 deletions(-)
 delete mode 100644 typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php

diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon
index 1f2aee62ec05..c3e889c5b358 100644
--- a/Build/phpstan/phpstan-baseline.neon
+++ b/Build/phpstan/phpstan-baseline.neon
@@ -1,10 +1,5 @@
 parameters:
 	ignoreErrors:
-		-
-			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Context\\\\AspectInterface\\:\\:isPreview\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
-
 		-
 			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Resource\\\\FolderInterface\\:\\:getCombinedIdentifier\\(\\)\\.$#"
 			count: 1
diff --git a/typo3/sysext/adminpanel/Classes/Controller/AjaxController.php b/typo3/sysext/adminpanel/Classes/Controller/AjaxController.php
index c743eb75001f..6009e39602ec 100644
--- a/typo3/sysext/adminpanel/Classes/Controller/AjaxController.php
+++ b/typo3/sysext/adminpanel/Classes/Controller/AjaxController.php
@@ -22,7 +22,6 @@ use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Http\JsonResponse;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Admin Panel Ajax Controller - Route endpoint for ajax actions
@@ -32,16 +31,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class AjaxController
 {
     protected array $adminPanelModuleConfiguration;
-    protected ModuleLoader $moduleLoader;
-    private ConfigurationService $configurationService;
 
-    public function __construct(?ConfigurationService $configurationService = null, ?ModuleLoader $moduleLoader = null)
-    {
-        $this->configurationService = $configurationService
-                                      ??
-                                      GeneralUtility::makeInstance(ConfigurationService::class);
+    public function __construct(
+        private readonly ConfigurationService $configurationService,
+        private readonly ModuleLoader $moduleLoader,
+    ) {
         $this->adminPanelModuleConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] ?? [];
-        $this->moduleLoader = $moduleLoader ?? GeneralUtility::makeInstance(ModuleLoader::class);
     }
 
     /**
diff --git a/typo3/sysext/adminpanel/Classes/Controller/MainController.php b/typo3/sysext/adminpanel/Classes/Controller/MainController.php
index bfa0129312db..a37fe53e5180 100644
--- a/typo3/sysext/adminpanel/Classes/Controller/MainController.php
+++ b/typo3/sysext/adminpanel/Classes/Controller/MainController.php
@@ -26,46 +26,35 @@ use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\RequestEnricherInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
-use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
 use TYPO3\CMS\Adminpanel\Utility\ResourceUtility;
 use TYPO3\CMS\Adminpanel\Utility\StateUtility;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
-use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
- * Main controller for the admin panel
+ * Main controller for the admin panel.
+ *
+ * Note this is a "shared" / singleton object: Middleware AdminPanelInitiator
+ * instantiates the object and eventually calls initialize(). Later,
+ * AdminPanelRenderer calls render() on the same object.
  *
  * @internal
  */
-class MainController implements SingletonInterface
+class MainController
 {
-    /**
-     * @var array<string, ModuleInterface>
-     */
+    /** @var array<string, ModuleInterface> */
     protected array $modules = [];
-
-    protected ModuleLoader $moduleLoader;
-    protected UriBuilder $uriBuilder;
-    protected ConfigurationService $configurationService;
     protected array $adminPanelModuleConfiguration;
 
     public function __construct(
-        ?ModuleLoader $moduleLoader = null,
-        ?UriBuilder $uriBuilder = null,
-        ?ConfigurationService $configurationService = null
+        private readonly ModuleLoader $moduleLoader,
+        private readonly UriBuilder $uriBuilder,
     ) {
-        $this->moduleLoader = $moduleLoader ?? GeneralUtility::makeInstance(ModuleLoader::class);
-        $this->uriBuilder = $uriBuilder ?? GeneralUtility::makeInstance(UriBuilder::class);
-        $this->configurationService = $configurationService
-                                      ?? GeneralUtility::makeInstance(ConfigurationService::class);
-        $adminPanelModuleConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] ?? [];
-        $this->adminPanelModuleConfiguration = is_array($adminPanelModuleConfiguration)
-            ? $adminPanelModuleConfiguration : [];
+        $this->adminPanelModuleConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] ?? [];
     }
 
     /**
@@ -76,7 +65,6 @@ class MainController implements SingletonInterface
         $this->modules = $this->moduleLoader->validateSortAndInitializeModules(
             $this->adminPanelModuleConfiguration
         );
-
         if (StateUtility::isActivatedForUser()) {
             $request = $this->initializeModules($request, $this->modules);
         }
diff --git a/typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php b/typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php
deleted file mode 100644
index b47b9b5d0092..000000000000
--- a/typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Adminpanel\Exceptions;
-
-/**
- * Exception class for invalid admin panel configuration
- */
-class InvalidConfigurationException extends \RuntimeException
-{
-}
diff --git a/typo3/sysext/adminpanel/Classes/Log/DoctrineSqlLogger.php b/typo3/sysext/adminpanel/Classes/Log/DoctrineSqlLogger.php
index 52dc67f905fb..63406d70e1b7 100644
--- a/typo3/sysext/adminpanel/Classes/Log/DoctrineSqlLogger.php
+++ b/typo3/sysext/adminpanel/Classes/Log/DoctrineSqlLogger.php
@@ -29,31 +29,14 @@ class DoctrineSqlLogger implements SQLLogger, LoggerAwareInterface
 {
     use LoggerAwareTrait;
 
-    /**
-     * Executed SQL queries.
-     *
-     * @var array
-     */
-    protected $queries = [];
+    /** Executed SQL queries. */
+    protected array $queries = [];
+    /** If Debug Stack is enabled (log queries) or not. */
+    protected bool $enabled = true;
+    protected float $start;
+    protected int $currentQuery = 0;
 
-    /**
-     * If Debug Stack is enabled (log queries) or not.
-     *
-     * @var bool
-     */
-    protected $enabled = true;
-
-    /**
-     * @var float
-     */
-    protected $start;
-
-    /**
-     * @var int
-     */
-    protected $currentQuery = 0;
-
-    public function startQuery($sql, array $params = null, array $types = null)
+    public function startQuery($sql, array $params = null, array $types = null): void
     {
         if ($this->enabled && MemoryUtility::isMemoryConsumptionTooHigh()) {
             $this->enabled = false;
@@ -78,7 +61,7 @@ class DoctrineSqlLogger implements SQLLogger, LoggerAwareInterface
         }
     }
 
-    public function stopQuery()
+    public function stopQuery(): void
     {
         if ($this->enabled) {
             $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start;
diff --git a/typo3/sysext/adminpanel/Classes/Log/InMemoryLogWriter.php b/typo3/sysext/adminpanel/Classes/Log/InMemoryLogWriter.php
index dc5fe97c07f5..4ae09603a55f 100644
--- a/typo3/sysext/adminpanel/Classes/Log/InMemoryLogWriter.php
+++ b/typo3/sysext/adminpanel/Classes/Log/InMemoryLogWriter.php
@@ -24,28 +24,24 @@ use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\ApplicationType;
 use TYPO3\CMS\Core\Log\LogRecord;
 use TYPO3\CMS\Core\Log\Writer\AbstractWriter;
+use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Log writer that writes the log records into a static public class variable
- * for InMemory processing
+ * for InMemory processing. Note this implements SingletonInterface so multiple
+ * calls to this class can accumulate log records in the same instance.
+ *
+ * @internal
  */
-class InMemoryLogWriter extends AbstractWriter
+final class InMemoryLogWriter extends AbstractWriter implements SingletonInterface
 {
-    /**
-     * @var LogRecord[]
-     */
-    public static $log = [];
-
-    /**
-     * @var bool
-     */
-    private static $memoryLock = false;
+    /** @var LogRecord[] */
+    private array $log = [];
+    private bool $memoryLock = false;
 
     /**
      * Writes the log record
-     *
-     * @param LogRecord $record Log record
      */
     public function writeLog(LogRecord $record): self
     {
@@ -53,7 +49,7 @@ class InMemoryLogWriter extends AbstractWriter
         if (Environment::isCli()
             || !(($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof  ServerRequestInterface)
             || !ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
-            || self::$memoryLock === true
+            || $this->memoryLock === true
         ) {
             return $this;
         }
@@ -64,23 +60,31 @@ class InMemoryLogWriter extends AbstractWriter
             return $this;
         }
 
-        self::$log[] = (clone $record)->setMessage($this->interpolate($record->getMessage(), $record->getData()));
+        $this->log[] = (clone $record)->setMessage($this->interpolate($record->getMessage(), $record->getData()));
 
         return $this;
     }
 
+    /**
+     * @return LogRecord[]
+     */
+    public function getLogEntries(): array
+    {
+        return $this->log;
+    }
+
     /**
      * Lock writer and add an info message that there may potentially be more entries.
      */
-    protected function lockWriter(): void
+    private function lockWriter(): void
     {
-        self::$memoryLock = true;
+        $this->memoryLock = true;
         $record = GeneralUtility::makeInstance(
             LogRecord::class,
             'TYPO3.CMS.AdminPanel.Log.InMemoryLogWriter',
             LogLevel::INFO,
             '... Further log entries omitted, memory usage too high.'
         );
-        self::$log[] = $record;
+        $this->log[] = $record;
     }
 }
diff --git a/typo3/sysext/adminpanel/Classes/Middleware/SqlLogging.php b/typo3/sysext/adminpanel/Classes/Middleware/SqlLogging.php
index 90863809305f..7c9146fe2bfa 100644
--- a/typo3/sysext/adminpanel/Classes/Middleware/SqlLogging.php
+++ b/typo3/sysext/adminpanel/Classes/Middleware/SqlLogging.php
@@ -33,14 +33,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 class SqlLogging implements MiddlewareInterface
 {
-    /**
-     * @var ConnectionPool
-     */
-    protected $connectionPool;
-
-    public function __construct(ConnectionPool $connectionPool)
-    {
-        $this->connectionPool = $connectionPool;
+    public function __construct(
+        private readonly ConnectionPool $connectionPool
+    ) {
     }
 
     /**
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php
index c2a5015796ea..3f62e5482b69 100644
--- a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php
+++ b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php
@@ -20,7 +20,6 @@ namespace TYPO3\CMS\Adminpanel\ModuleApi;
 use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Abstract base class for Admin Panel Modules containing helper methods and default interface implementations
@@ -28,34 +27,23 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 abstract class AbstractModule implements ModuleInterface, ConfigurableInterface, SubmoduleProviderInterface
 {
-    /**
-     * @var ModuleInterface[]
-     */
-    protected $subModules = [];
-
-    /**
-     * Main Configuration (from UserTSConfig, admPanel)
-     *
-     * @var array
-     */
-    protected $mainConfiguration;
-
-    /**
-     * @var ConfigurationService
-     */
-    protected $configurationService;
+    /** @var ModuleInterface[] */
+    protected array $subModules = [];
+    /** Main Configuration (from UserTSConfig, admPanel) */
+    protected array $mainConfiguration;
+    protected ConfigurationService $configurationService;
 
-    public function __construct()
+    public function injectConfigurationService(ConfigurationService $configurationService): void
     {
-        $this->configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
+        $this->configurationService = $configurationService;
         $this->mainConfiguration = $this->configurationService->getMainConfiguration();
     }
 
     /**
      * Returns true if the module is
-     * -> either enabled via TSConfig admPanel.enable
-     * -> or any setting is overridden
-     * override is a way to use functionality of the admin panel without displaying the admin panel to users
+     * -> either enabled via TSConfig "admPanel.enable"
+     * -> or any setting is overridden.
+     * Override is a way to use functionality of the admin panel without displaying the admin panel to users
      * for example: hidden records or pages can be displayed by default
      */
     public function isEnabled(): bool
@@ -104,7 +92,7 @@ abstract class AbstractModule implements ModuleInterface, ConfigurableInterface,
     }
 
     /**
-     * Returns true if TSConfig admPanel.enable is set for this module (or all modules)
+     * Returns true if TSConfig "admPanel.enable" is set for this module (or all modules)
      */
     protected function isEnabledViaTsConfig(): bool
     {
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php
index dfa5ab615527..62f4aaf440a0 100644
--- a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php
+++ b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php
@@ -21,9 +21,9 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Localization\LanguageService;
 
 /**
- * Abstract SubModule - Base class for sub modules in the admin panel
+ * Abstract SubModule - Base class for submodules in the admin panel
  *
- * Extend this class when writing own sub modules
+ * Extend this class when writing own submodules
  */
 abstract class AbstractSubModule implements ModuleInterface, ContentProviderInterface
 {
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php
index 5ebf56190d66..365cf5d87201 100644
--- a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php
+++ b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php
@@ -21,7 +21,7 @@ namespace TYPO3\CMS\Adminpanel\ModuleApi;
  * ModuleData is a simple wrapper object which extends ArrayObject
  * which is used to hold Adminpanel module data
  *
- * It's a separate class to add semantic meaning to its' usage
+ * It's a separate class to add semantic meaning to its usage
  */
 class ModuleData extends \ArrayObject
 {
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php
index 759b68b52fd8..9ec3df6e99d4 100644
--- a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php
+++ b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php
@@ -27,10 +27,7 @@ class ModuleDataStorageCollection extends \SplObjectStorage
         $this->attach($module, $moduleData);
     }
 
-    /**
-     * @param object $object
-     */
-    public function getHash($object): string
+    public function getHash(object $object): string
     {
         if ($object instanceof ModuleInterface) {
             return $object->getIdentifier();
diff --git a/typo3/sysext/adminpanel/Classes/Modules/Debug/Log.php b/typo3/sysext/adminpanel/Classes/Modules/Debug/Log.php
index a784d7c57686..7ea1f9f441b7 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/Debug/Log.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/Debug/Log.php
@@ -37,12 +37,11 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 class Log extends AbstractSubModule implements DataProviderInterface, ModuleSettingsProviderInterface, RequestEnricherInterface
 {
     protected int $logLevel;
-    protected ConfigurationService $configurationService;
 
-    public function __construct()
-    {
+    public function __construct(
+        private readonly ConfigurationService $configurationService,
+    ) {
         $this->logLevel = LogLevel::normalizeLevel(\Psr\Log\LogLevel::INFO);
-        $this->configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
     }
 
     public function getIdentifier(): string
@@ -71,10 +70,10 @@ class Log extends AbstractSubModule implements DataProviderInterface, ModuleSett
             ];
         }
 
-        $log = InMemoryLogWriter::$log;
+        $logRecords = GeneralUtility::makeInstance(InMemoryLogWriter::class)->getLogEntries();
 
         $logArray = [];
-        foreach ($log as $logRecord) {
+        foreach ($logRecords as $logRecord) {
             $entry = $logRecord->toArray();
             // store only necessary info
             unset($entry['data']);
diff --git a/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php b/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
index 132c2c6196b9..a5a98835d3dc 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
@@ -22,6 +22,7 @@ use TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter;
 use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
 use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
@@ -68,8 +69,8 @@ class PageTitle extends AbstractSubModule implements DataProviderInterface
                 'skippedProviders' => [],
             ];
 
-            $log = InMemoryLogWriter::$log;
-            foreach ($log as $logEntry) {
+            $logRecords = GeneralUtility::makeInstance(InMemoryLogWriter::class)->getLogEntries();
+            foreach ($logRecords as $logEntry) {
                 if ($logEntry->getComponent() === self::LOG_COMPONENT) {
                     $logEntryData = $logEntry->getData();
                     if (isset($logEntryData['orderedTitleProviders'])) {
diff --git a/typo3/sysext/adminpanel/Classes/Modules/DebugModule.php b/typo3/sysext/adminpanel/Classes/Modules/DebugModule.php
index bffa30ddaffd..cc6fd3911844 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/DebugModule.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/DebugModule.php
@@ -22,6 +22,7 @@ use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
 use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
 use TYPO3\CMS\Core\Log\LogLevel;
 use TYPO3\CMS\Core\Log\LogRecord;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Debug Module of the AdminPanel
@@ -47,7 +48,8 @@ class DebugModule extends AbstractModule implements ShortInfoProviderInterface
 
     public function getShortInfo(): string
     {
-        $errorsAndWarnings = array_filter(InMemoryLogWriter::$log, static function (LogRecord $entry) {
+        $logRecords = GeneralUtility::makeInstance(InMemoryLogWriter::class)->getLogEntries();
+        $errorsAndWarnings = array_filter($logRecords, static function (LogRecord $entry) {
             return LogLevel::normalizeLevel($entry->getLevel()) <= 4;
         });
         return sprintf($this->getLanguageService()->sL(
diff --git a/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php b/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
index ae03bf02eb30..b7f8c1f8fb9c 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
@@ -49,7 +49,7 @@ class PreviewModule extends AbstractModule implements RequestEnricherInterface,
      *     showFluidDebug?: bool
      * }
      */
-    protected $config;
+    protected array $config;
 
     public function getIconIdentifier(): string
     {
@@ -198,6 +198,7 @@ class PreviewModule extends AbstractModule implements RequestEnricherInterface,
         }
         $isPreview = $simulateUserGroup || $simTime || $showHiddenPages || $showHiddenRecords || $showScheduledRecords;
         if ($context->hasAspect('frontend.preview')) {
+            /** @var PreviewAspect $existingPreviewAspect */
             $existingPreviewAspect = $context->getAspect('frontend.preview');
             $isPreview = $existingPreviewAspect->isPreview() || $isPreview;
         }
diff --git a/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php b/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
index 3608c3d2b53c..2abd7b560fa1 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
@@ -36,11 +36,9 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
  */
 class TypoScriptWaterfall extends AbstractSubModule implements RequestEnricherInterface, ModuleSettingsProviderInterface
 {
-    protected ConfigurationService $configurationService;
-
-    public function __construct()
-    {
-        $this->configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
+    public function __construct(
+        private readonly ConfigurationService $configurationService,
+    ) {
     }
 
     public function getIdentifier(): string
diff --git a/typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php b/typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php
index b36200cc4b54..930aca99ddac 100644
--- a/typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php
+++ b/typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php
@@ -94,13 +94,10 @@ class ConfigurationService implements SingletonInterface
         return $GLOBALS['BE_USER'];
     }
 
-    /**
-     * @param array $configurationToSave
-     */
     protected function triggerOnSubmitActors(
         array $modules,
         ServerRequestInterface $request,
-        $configurationToSave
+        array $configurationToSave
     ): void {
         foreach ($modules as $module) {
             if (
diff --git a/typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php b/typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php
index 65e03af860e1..21829ecd1e62 100644
--- a/typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php
+++ b/typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php
@@ -17,7 +17,6 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Adminpanel\Service;
 
-use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
 use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
@@ -45,7 +44,7 @@ class ModuleLoader
         }
         foreach ($modules as $identifier => $configuration) {
             if (empty($configuration) || !is_array($configuration)) {
-                throw new InvalidConfigurationException(
+                throw new \RuntimeException(
                     'Missing configuration for module "' . $identifier . '".',
                     1519490105
                 );
@@ -59,7 +58,7 @@ class ModuleLoader
                     true
                 )
             ) {
-                throw new InvalidConfigurationException(
+                throw new \RuntimeException(
                     'The module "' .
                     $identifier .
                     '" defines an invalid module class. Ensure the class exists and implements the "' .
diff --git a/typo3/sysext/adminpanel/Configuration/Services.yaml b/typo3/sysext/adminpanel/Configuration/Services.yaml
index e7f368488d86..cd9d118b591c 100644
--- a/typo3/sysext/adminpanel/Configuration/Services.yaml
+++ b/typo3/sysext/adminpanel/Configuration/Services.yaml
@@ -12,6 +12,25 @@ services:
     factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
     arguments: ['adminpanel_requestcache']
 
+  TYPO3\CMS\Adminpanel\Controller\AjaxController:
+    public: true
+  TYPO3\CMS\Adminpanel\Controller\MainController:
+    public: true
+
+  TYPO3\CMS\Adminpanel\Modules\CacheModule:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\DebugModule:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\InfoModule:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\PreviewModule:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\TsDebugModule:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\Debug\Log:
+    public: true
+  TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall:
+    public: true
 
   Psr\EventDispatcher\EventDispatcherInterface:
     alias: TYPO3\CMS\Adminpanel\Service\EventDispatcher
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php
index bba8076625bf..7e5803c45018 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php
@@ -40,8 +40,6 @@ class MainModuleFixture implements
     /**
      * Identifier for this module,
      * for example "preview" or "cache"
-     *
-     * @return string
      */
     public function getIdentifier(): string
     {
@@ -50,8 +48,6 @@ class MainModuleFixture implements
 
     /**
      * Module label
-     *
-     * @return string
      */
     public function getLabel(): string
     {
@@ -60,8 +56,6 @@ class MainModuleFixture implements
 
     /**
      * Module Icon identifier - needs to be registered in iconRegistry
-     *
-     * @return string
      */
     public function getIconIdentifier(): string
     {
@@ -70,17 +64,12 @@ class MainModuleFixture implements
 
     /**
      * Displayed directly in the bar if module has content
-     *
-     * @return string
      */
     public function getShortInfo(): string
     {
         return 'short info';
     }
 
-    /**
-     * @return string
-     */
     public function getPageSettings(): string
     {
         return 'example settings';
@@ -92,8 +81,6 @@ class MainModuleFixture implements
      * A module may be enabled but not shown
      * -> only the initializeModule() method
      * will be called
-     *
-     * @return bool
      */
     public function isEnabled(): bool
     {
@@ -104,9 +91,6 @@ class MainModuleFixture implements
      * Executed on saving / submit of the configuration form
      * Can be used to react to changed settings
      * (for example: clearing a specific cache)
-     *
-     * @param array $configurationToSave
-     * @param ServerRequestInterface $request
      */
     public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
     {
@@ -114,8 +98,6 @@ class MainModuleFixture implements
 
     /**
      * Returns a string array with javascript files that will be rendered after the module
-     *
-     * @return array
      */
     public function getJavaScriptFiles(): array
     {
@@ -124,8 +106,6 @@ class MainModuleFixture implements
 
     /**
      * Returns a string array with css files that will be rendered after the module
-     *
-     * @return array
      */
     public function getCssFiles(): array
     {
@@ -159,9 +139,6 @@ class MainModuleFixture implements
     /**
      * Initialize the module - runs in the TYPO3 middleware stack at an early point
      * may manipulate the current request
-     *
-     * @param ServerRequestInterface $request
-     * @return ServerRequestInterface
      */
     public function enrich(ServerRequestInterface $request): ServerRequestInterface
     {
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php
index 769647a760f0..483e4cf94e45 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php
@@ -30,8 +30,6 @@ class SubModuleFixture implements ModuleInterface, ContentProviderInterface, Mod
     /**
      * Identifier for this Sub-module,
      * for example "preview" or "cache"
-     *
-     * @return string
      */
     public function getIdentifier(): string
     {
@@ -40,8 +38,6 @@ class SubModuleFixture implements ModuleInterface, ContentProviderInterface, Mod
 
     /**
      * Sub-Module label
-     *
-     * @return string
      */
     public function getLabel(): string
     {
@@ -50,9 +46,6 @@ class SubModuleFixture implements ModuleInterface, ContentProviderInterface, Mod
 
     /**
      * Sub-Module content as rendered HTML
-     *
-     * @param ModuleData $data
-     * @return string
      */
     public function getContent(ModuleData $data): string
     {
@@ -61,8 +54,6 @@ class SubModuleFixture implements ModuleInterface, ContentProviderInterface, Mod
 
     /**
      * Settings as HTML form elements (without wrapping form tag or save button)
-     *
-     * @return string
      */
     public function getSettings(): string
     {
@@ -73,18 +64,11 @@ class SubModuleFixture implements ModuleInterface, ContentProviderInterface, Mod
      * Executed on saving / submit of the configuration form
      * Can be used to react to changed settings
      * (for example: clearing a specific cache)
-     *
-     * @param array $configurationToSave
-     * @param ServerRequestInterface $request
      */
     public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
     {
     }
 
-    /**
-     * @param ServerRequestInterface $request
-     * @return ModuleData
-     */
     public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
         return new ModuleData(['foo' => 'bar']);
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Middleware/AdminPanelInitiatorTest.php b/typo3/sysext/adminpanel/Tests/Unit/Middleware/AdminPanelInitiatorTest.php
index e752af23b043..168f989b9cc3 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Middleware/AdminPanelInitiatorTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Middleware/AdminPanelInitiatorTest.php
@@ -29,8 +29,6 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 class AdminPanelInitiatorTest extends UnitTestCase
 {
-    protected bool $resetSingletonInstances = true;
-
     /**
      * @test
      */
@@ -54,7 +52,7 @@ class AdminPanelInitiatorTest extends UnitTestCase
         $GLOBALS['BE_USER'] = $userAuthentication;
 
         $controller = $this->getMockBuilder(MainController::class)->disableOriginalConstructor()->getMock();
-        GeneralUtility::setSingletonInstance(MainController::class, $controller);
+        GeneralUtility::addInstance(MainController::class, $controller);
         $handler = $this->getHandlerMock();
         $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock();
         $request->expects(self::any())->method('withAttribute')->withAnyParameters()->willReturn($request);
@@ -100,10 +98,6 @@ class AdminPanelInitiatorTest extends UnitTestCase
         $this->checkAdminPanelDoesNotCallInitialize($tsConfig, $uc);
     }
 
-    /**
-     * @param array $tsConfig
-     * @param array $uc
-     */
     protected function checkAdminPanelDoesNotCallInitialize(array $tsConfig, array $uc): void
     {
         $userAuthentication = $this->getMockBuilder(FrontendBackendUserAuthentication::class)->getMock();
@@ -112,14 +106,16 @@ class AdminPanelInitiatorTest extends UnitTestCase
         $GLOBALS['BE_USER'] = $userAuthentication;
 
         $controller = $this->getMockBuilder(MainController::class)->disableOriginalConstructor()->getMock();
-        GeneralUtility::setSingletonInstance(MainController::class, $controller);
+        GeneralUtility::addInstance(MainController::class, $controller);
         $handler = $this->getHandlerMock();
         $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock();
+        $controller->expects(self::never())->method('initialize');
 
         $adminPanelInitiator = new AdminPanelInitiator();
         $adminPanelInitiator->process($request, $handler);
 
-        $controller->expects(self::never())->method('initialize');
+        /** @var MainController&MockObject $controller */
+        $controller = GeneralUtility::makeInstance(MainController::class);
     }
 
     protected function getHandlerMock(): RequestHandlerInterface&MockObject
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php b/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
index f4ffbd2f267a..865bc223ccb2 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
@@ -63,9 +63,8 @@ class PreviewModuleTest extends UnitTestCase
         ];
         $configurationService->method('getConfigurationOption')->withAnyParameters()->willReturnMap($valueMap);
 
-        GeneralUtility::setSingletonInstance(ConfigurationService::class, $configurationService);
-
         $previewModule = new PreviewModule();
+        $previewModule->injectConfigurationService($configurationService);
         $previewModule->enrich(new ServerRequest());
 
         self::assertSame($GLOBALS['SIM_EXEC_TIME'], $expectedExecTime, 'EXEC_TIME');
@@ -90,7 +89,6 @@ class PreviewModuleTest extends UnitTestCase
             ['preview', 'showFluidDebug', '0'],
         ];
         $configurationService->method('getConfigurationOption')->withAnyParameters()->willReturnMap($valueMap);
-        GeneralUtility::setSingletonInstance(ConfigurationService::class, $configurationService);
 
         $context = $this->getMockBuilder(Context::class)->getMock();
         $context->method('hasAspect')->with('frontend.preview')->willReturn(false);
@@ -105,6 +103,7 @@ class PreviewModuleTest extends UnitTestCase
         GeneralUtility::setSingletonInstance(Context::class, $context);
 
         $previewModule = new PreviewModule();
+        $previewModule->injectConfigurationService($configurationService);
         $previewModule->enrich($request);
     }
 }
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php b/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php
index c26089ccfe05..aff1200e8a82 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php
@@ -127,8 +127,6 @@ class ConfigurationServiceTest extends UnitTestCase
     /**
      * @test
      * @dataProvider getConfigurationOptionEmptyArgumentDataProvider
-     * @param string $identifier
-     * @param string $option
      */
     public function getConfigurationOptionThrowsExceptionOnEmptyArgument(string $identifier, string $option): void
     {
@@ -216,9 +214,6 @@ class ConfigurationServiceTest extends UnitTestCase
         self::assertSame($expected, $this->beUser->uc);
     }
 
-    /**
-     * @param array $userTsAdmPanelConfig
-     */
     private function setUpUserTsConfigForAdmPanel(array $userTsAdmPanelConfig): void
     {
         $this->beUser->method('getTSConfig')->willReturn(
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php b/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php
index 9fe70fcc2cde..31e8eca8c97b 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php
@@ -17,7 +17,6 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Adminpanel\Tests\Unit\Service;
 
-use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
 use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
 use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\DisabledMainModuleFixture;
 use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\MainModuleFixture;
@@ -49,11 +48,10 @@ class ModuleLoaderTest extends UnitTestCase
     /**
      * @test
      * @dataProvider missingConfigurationDataProvider
-     * @param array $configuration
      */
     public function validateSortAndInitializeModulesThrowsExceptionIfModuleHasMissingConfiguration(array $configuration): void
     {
-        $this->expectException(InvalidConfigurationException::class);
+        $this->expectException(\RuntimeException::class);
         $this->expectExceptionCode(1519490105);
 
         $moduleLoader = new ModuleLoader();
@@ -88,12 +86,11 @@ class ModuleLoaderTest extends UnitTestCase
 
     /**
      * @test
-     * @dataProvider  invalidConfigurationDataProvider
-     * @param array $configuration
+     * @dataProvider invalidConfigurationDataProvider
      */
     public function validateSortAndInitializeModulesThrowsExceptionIfModuleHasInvalidConfiguration(array $configuration): void
     {
-        $this->expectException(InvalidConfigurationException::class);
+        $this->expectException(\RuntimeException::class);
         $this->expectExceptionCode(1519490112);
 
         $moduleLoader = new ModuleLoader();
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Utility/StateUtilityTest.php b/typo3/sysext/adminpanel/Tests/Unit/Utility/StateUtilityTest.php
index 1a15f83d4e86..ee81f4d92f51 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Utility/StateUtilityTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Utility/StateUtilityTest.php
@@ -71,7 +71,6 @@ class StateUtilityTest extends UnitTestCase
     /**
      * @test
      * @dataProvider tsConfigEnabledDataProvider
-     * @param array $tsConfig
      */
     public function isEnabledReturnsTrueIfAtLeastOneModuleIsEnabled(array $tsConfig): void
     {
@@ -108,7 +107,6 @@ class StateUtilityTest extends UnitTestCase
     /**
      * @test
      * @dataProvider tsConfigDisabledDataProvider
-     * @param array $tsConfig
      */
     public function isEnabledReturnsFalseIfNoModulesEnabled(array $tsConfig): void
     {
@@ -148,8 +146,6 @@ class StateUtilityTest extends UnitTestCase
     /**
      * @test
      * @dataProvider tsConfigHideDataProvider
-     * @param array $tsConfig
-     * @param bool $expected
      */
     public function isHiddenForUserReturnsCorrectValue(array $tsConfig, bool $expected): void
     {
@@ -199,8 +195,6 @@ class StateUtilityTest extends UnitTestCase
     /**
      * @test
      * @dataProvider ucDisplayOpenDataProvider
-     * @param array $uc
-     * @param bool $expected
      */
     public function isOpenForUserReturnsCorrectValue(array $uc, bool $expected): void
     {
@@ -250,8 +244,6 @@ class StateUtilityTest extends UnitTestCase
     /**
      * @test
      * @dataProvider typoScriptDataProvider
-     * @param array $typoScript
-     * @param bool $expected
      */
     public function isActivatedInTypoScriptReturnsCorrectValue(array $typoScript, bool $expected): void
     {
-- 
GitLab