From 98cf5d6947507648961368b479d353ae39bca762 Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <bfr@qbus.de>
Date: Fri, 29 May 2020 20:37:43 +0200
Subject: [PATCH] [!!!][TASK] Remove support for deprecated CLI command
 configuration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Configuration via Commands.php had been superseded by tag based
DI service configuration which allowed to configure DI and dependencies
for CLI commands in one place.

The CommandRegistry is refactored as the runtime merging logic
can now be dropped and handled in addLazyCommand – which is called
by the symfony DI generated factory – instead.

Resolves: #91534
Related: #91473
Related: #89139
Releases: master
Change-Id: I24fc554902650c66b8dc397708d80968514c6744
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64603
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
---
 .../Classes/Console/CommandApplication.php    |  15 --
 .../CommandNameAlreadyInUseException.php      |   6 +-
 .../core/Classes/Console/CommandRegistry.php  | 165 ++--------------
 .../Classes/Console/CommandRequestHandler.php | 113 -----------
 .../Console/RequestHandlerInterface.php       |  55 ------
 typo3/sysext/core/Classes/ServiceProvider.php |   2 +-
 ...g-91473-DeprecatedFunctionalityRemoved.rst |   6 +-
 .../Unit/Console/CommandRegistryTest.php      |  64 +-----
 .../Console/CommandRegistryTest.php           | 183 ------------------
 .../ExtensionScanner/Php/ClassNameMatcher.php |   2 +
 .../Php/MethodCallMatcher.php                 |   1 +
 11 files changed, 29 insertions(+), 583 deletions(-)
 delete mode 100644 typo3/sysext/core/Classes/Console/CommandRequestHandler.php
 delete mode 100644 typo3/sysext/core/Classes/Console/RequestHandlerInterface.php
 delete mode 100644 typo3/sysext/core/Tests/UnitDeprecated/Console/CommandRegistryTest.php

diff --git a/typo3/sysext/core/Classes/Console/CommandApplication.php b/typo3/sysext/core/Classes/Console/CommandApplication.php
index cd9741afe503..b2ead6b8cd53 100644
--- a/typo3/sysext/core/Classes/Console/CommandApplication.php
+++ b/typo3/sysext/core/Classes/Console/CommandApplication.php
@@ -86,8 +86,6 @@ class CommandApplication implements ApplicationInterface
         // Make sure output is not buffered, so command-line output and interaction can take place
         ob_clean();
 
-        $this->populateAvailableCommands();
-
         $exitCode = $this->application->run($input, $output);
 
         if ($execute !== null) {
@@ -117,17 +115,4 @@ class CommandApplication implements ApplicationInterface
         $this->context->setAspect('workspace', new WorkspaceAspect(0));
         $this->context->setAspect('backend.user', new UserAspect(null));
     }
-
-    /**
-     * Put all available commands inside the application
-     *
-     * Note: This method will be removed in TYPO3 v11 when support for Configuration/Commands.php is dropped.
-     */
-    protected function populateAvailableCommands(): void
-    {
-        foreach ($this->commandRegistry->getLegacyCommands() as $commandName => $command) {
-            /** @var Command $command */
-            $this->application->add($command);
-        }
-    }
 }
diff --git a/typo3/sysext/core/Classes/Console/CommandNameAlreadyInUseException.php b/typo3/sysext/core/Classes/Console/CommandNameAlreadyInUseException.php
index 5877d7bbf87a..0a3b2e0698f0 100644
--- a/typo3/sysext/core/Classes/Console/CommandNameAlreadyInUseException.php
+++ b/typo3/sysext/core/Classes/Console/CommandNameAlreadyInUseException.php
@@ -18,8 +18,10 @@ namespace TYPO3\CMS\Core\Console;
 use TYPO3\CMS\Core\Exception;
 
 /**
- * Exception thrown when a command is registered with a name
- * that is already taken
+ * Exception that was thrown when a command was registered with a name
+ * that is already taken. This exception is currently unused.
+ *
+ * @todo: deprecate
  */
 class CommandNameAlreadyInUseException extends Exception
 {
diff --git a/typo3/sysext/core/Classes/Console/CommandRegistry.php b/typo3/sysext/core/Classes/Console/CommandRegistry.php
index bb3ffaa72f11..c96656a230bf 100644
--- a/typo3/sysext/core/Classes/Console/CommandRegistry.php
+++ b/typo3/sysext/core/Classes/Console/CommandRegistry.php
@@ -21,32 +21,18 @@ use Psr\Container\ContainerInterface;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
 use Symfony\Component\Console\Exception\CommandNotFoundException;
-use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
- * Registry for Symfony commands, populated from extensions
+ * Registry for Symfony commands, populated via dependency injection tags
  */
-class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, SingletonInterface
+class CommandRegistry implements CommandLoaderInterface, SingletonInterface
 {
-    /**
-     * @var PackageManager
-     */
-    protected $packageManager;
-
     /**
      * @var ContainerInterface
      */
     protected $container;
 
-    /**
-     * Map of commands
-     *
-     * @var array
-     */
-    protected $commands = [];
-
     /**
      * Map of command configurations with the command name as key
      *
@@ -55,19 +41,10 @@ class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, Sin
     protected $commandConfigurations = [];
 
     /**
-     * Map of lazy (DI-managed) command configurations with the command name as key
-     *
-     * @var array
-     */
-    protected $lazyCommandConfigurations = [];
-
-    /**
-     * @param PackageManager $packageManager
      * @param ContainerInterface $container
      */
-    public function __construct(PackageManager $packageManager, ContainerInterface $container)
+    public function __construct(ContainerInterface $container)
     {
-        $this->packageManager = $packageManager;
         $this->container = $container;
     }
 
@@ -76,9 +53,7 @@ class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, Sin
      */
     public function has($name)
     {
-        $this->populateCommandsFromPackages();
-
-        return array_key_exists($name, $this->commands);
+        return array_key_exists($name, $this->commandConfigurations);
     }
 
     /**
@@ -98,26 +73,7 @@ class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, Sin
      */
     public function getNames()
     {
-        $this->populateCommandsFromPackages();
-
-        return array_keys($this->commands);
-    }
-
-    /**
-     * @return \Generator
-     * @deprecated will be removed in TYPO3 v11.0 when support for Configuration/Commands.php is dropped.
-     */
-    public function getIterator(): \Generator
-    {
-        trigger_error('Using ' . self::class . ' as iterable has been deprecated and will stop working in TYPO3 11.0.', E_USER_DEPRECATED);
-
-        $this->populateCommandsFromPackages();
-        foreach ($this->commands as $commandName => $command) {
-            if (is_string($command)) {
-                $command = $this->getInstance($command);
-            }
-            yield $commandName => $command;
-        }
+        return array_keys($this->commandConfigurations);
     }
 
     /**
@@ -127,126 +83,33 @@ class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, Sin
      */
     public function getSchedulableCommands(): \Generator
     {
-        $this->populateCommandsFromPackages();
-        foreach ($this->commands as $commandName => $command) {
-            if ($this->commandConfigurations[$commandName]['schedulable'] ?? true) {
-                if (is_string($command)) {
-                    $command = $this->getInstance($command);
-                }
-                yield $commandName => $command;
+        foreach ($this->commandConfigurations as $commandName => $configuration) {
+            if ($configuration['schedulable'] ?? true) {
+                yield $commandName => $this->getInstance($configuration['serviceName']);
             }
         }
     }
 
-    /**
-     * @return \Generator
-     * @internal This method will be removed in TYPO3 v11 when support for Configuration/Commands.php is dropped.
-     */
-    public function getLegacyCommands(): \Generator
-    {
-        $this->populateCommandsFromPackages();
-        foreach ($this->commands as $commandName => $command) {
-            // Type string indicates lazy loading
-            if (is_string($command)) {
-                continue;
-            }
-            yield $commandName => $command;
-        }
-    }
-
     /**
      * @param string $identifier
-     * @throws CommandNameAlreadyInUseException
      * @throws UnknownCommandException
      * @return Command
      */
     public function getCommandByIdentifier(string $identifier): Command
     {
-        $this->populateCommandsFromPackages();
-
-        if (!isset($this->commands[$identifier])) {
+        if (!isset($this->commandConfigurations[$identifier])) {
             throw new UnknownCommandException(
                 sprintf('Command "%s" has not been registered.', $identifier),
                 1510906768
             );
         }
 
-        $command = $this->commands[$identifier] ?? null;
-        if (is_string($command)) {
-            $command = $this->getInstance($command);
-        }
-
-        return $command;
-    }
-
-    /**
-     * Find all Configuration/Commands.php files of extensions and create a registry from it.
-     * The file should return an array with a command key as key and the command description
-     * as value. The command description must be an array and have a class key that defines
-     * the class name of the command. Example:
-     *
-     * <?php
-     * return [
-     *     'backend:lock' => [
-     *         'class' => \TYPO3\CMS\Backend\Command\LockBackendCommand::class
-     *     ],
-     * ];
-     *
-     * @throws CommandNameAlreadyInUseException
-     */
-    protected function populateCommandsFromPackages()
-    {
-        if ($this->commands) {
-            return;
-        }
-
-        foreach ($this->lazyCommandConfigurations as $commandName => $commandConfig) {
-            // Lazy commands shall be loaded from the Container on demand, store the command as string to indicate lazy loading
-            $this->commands[$commandName] = $commandConfig['class'];
-            $this->commandConfigurations[$commandName] = $commandConfig;
-        }
-
-        foreach ($this->packageManager->getActivePackages() as $package) {
-            $commandsOfExtension = $package->getPackagePath() . 'Configuration/Commands.php';
-            if (@is_file($commandsOfExtension)) {
-                /*
-                 * We use require instead of require_once here because it eases the testability as require_once returns
-                 * a boolean from the second execution on. As this class is a singleton, this require is only called
-                 * once per request anyway.
-                 */
-                $commands = require $commandsOfExtension;
-                if (is_array($commands)) {
-                    foreach ($commands as $commandName => $commandConfig) {
-                        if (array_key_exists($commandName, $this->lazyCommandConfigurations)) {
-                            // Lazy (DI managed) commands override classic commands from Configuration/Commands.php
-                            // Skip this case to allow extensions to provide commands via DI config and to allow
-                            // TYPO3 v9 backwards compatible configuration via Configuration/Commands.php.
-                            // Note: Also the deprecation error is skipped on-demand as the extension has been
-                            // adapted and the configuration will be ignored as of TYPO3 v11.
-                            continue;
-                        }
-                        if (array_key_exists($commandName, $this->commands)) {
-                            throw new CommandNameAlreadyInUseException(
-                                'Command "' . $commandName . '" registered by "' . $package->getPackageKey() . '" is already in use',
-                                1484486383
-                            );
-                        }
-                        $this->commands[$commandName] = GeneralUtility::makeInstance($commandConfig['class'], $commandName);
-                        $this->commandConfigurations[$commandName] = $commandConfig;
-
-                        trigger_error(
-                            'Registering console commands in Configuration/Commands.php has been deprecated and will stop working in TYPO3 v11.0.',
-                            E_USER_DEPRECATED
-                        );
-                    }
-                }
-            }
-        }
+        return $this->getInstance($this->commandConfigurations[$identifier]['serviceName']);
     }
 
-    protected function getInstance(string $class): Command
+    protected function getInstance(string $service): Command
     {
-        return $this->container->get($class);
+        return $this->container->get($service);
     }
 
     /**
@@ -254,8 +117,8 @@ class CommandRegistry implements CommandLoaderInterface, \IteratorAggregate, Sin
      */
     public function addLazyCommand(string $commandName, string $serviceName, bool $schedulable = true): void
     {
-        $this->lazyCommandConfigurations[$commandName] = [
-            'class' => $serviceName,
+        $this->commandConfigurations[$commandName] = [
+            'serviceName' => $serviceName,
             'schedulable' => $schedulable,
         ];
     }
diff --git a/typo3/sysext/core/Classes/Console/CommandRequestHandler.php b/typo3/sysext/core/Classes/Console/CommandRequestHandler.php
deleted file mode 100644
index 2216a766e07c..000000000000
--- a/typo3/sysext/core/Classes/Console/CommandRequestHandler.php
+++ /dev/null
@@ -1,113 +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\Core\Console;
-
-use Symfony\Component\Console\Application;
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\ConsoleOutput;
-use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Core\Environment;
-use TYPO3\CMS\Core\Information\Typo3Version;
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Command Line Interface Request Handler dealing with registered commands.
- *
- * @deprecated since TYPO3 v10.1, will be removed in TYPO3 v11.0, as everything is handled by the CommandApplication directly.
- */
-class CommandRequestHandler implements RequestHandlerInterface
-{
-    /**
-     * Instance of the symfony application
-     * @var Application
-     */
-    protected $application;
-
-    /**
-     * Constructor initializing the symfony application
-     */
-    public function __construct()
-    {
-        trigger_error('CommandRequestHandler will be removed in TYPO3 v11.0, as CLI is executed inside the CommandApplication directly.', E_USER_DEPRECATED);
-        $this->application = new Application('TYPO3 CMS', sprintf(
-            '%s (Application Context: <comment>%s</comment>)',
-            (new Typo3Version())->getVersion(),
-            Environment::getContext()
-        ));
-    }
-
-    /**
-     * Handles any commandline request
-     *
-     * @param InputInterface $input
-     */
-    public function handleRequest(InputInterface $input)
-    {
-        $output = new ConsoleOutput();
-
-        Bootstrap::loadExtTables();
-        // create the BE_USER object (not logged in yet)
-        Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
-        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
-        // Make sure output is not buffered, so command-line output and interaction can take place
-        ob_clean();
-
-        $this->populateAvailableCommands();
-
-        $exitCode = $this->application->run($input, $output);
-        exit($exitCode);
-    }
-
-    /**
-     * This request handler can handle any CLI request
-     *
-     * @param InputInterface $input
-     * @return bool Always TRUE
-     */
-    public function canHandleRequest(InputInterface $input): bool
-    {
-        return true;
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority(): int
-    {
-        return 50;
-    }
-
-    /**
-     * Put all available commands inside the application
-     * @throws \TYPO3\CMS\Core\Console\CommandNameAlreadyInUseException
-     */
-    protected function populateAvailableCommands()
-    {
-        $commands = GeneralUtility::makeInstance(CommandRegistry::class);
-
-        foreach ($commands as $commandName => $command) {
-            /** @var Command $command */
-            $this->application->add($command);
-        }
-    }
-}
diff --git a/typo3/sysext/core/Classes/Console/RequestHandlerInterface.php b/typo3/sysext/core/Classes/Console/RequestHandlerInterface.php
deleted file mode 100644
index 2ae8fd887d2e..000000000000
--- a/typo3/sysext/core/Classes/Console/RequestHandlerInterface.php
+++ /dev/null
@@ -1,55 +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\Core\Console;
-
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-
-/**
- * The interface for a request handler for a console-based application
- *
- * @deprecated not in use any since TYPO3 v10.0, will be removed in TYPO3 v11.0
- */
-interface RequestHandlerInterface
-{
-    /**
-     * Handles a CLI request
-     *
-     * @param InputInterface $input
-     * @return OutputInterface|null
-     */
-    public function handleRequest(InputInterface $input);
-
-    /**
-     * Checks if the request handler can handle the given request.
-     *
-     * @param InputInterface $input
-     * @return bool TRUE if it can handle the request, otherwise FALSE
-     */
-    public function canHandleRequest(InputInterface $input);
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the
-     * request. An integer > 0 means "I want to handle this request" where
-     * "100" is default. "0" means "I am a fallback solution".
-     *
-     * @return int The priority of the request handler
-     * @internal will get removed from the interface soon as this is not needed in TYPO3 v10.0 anymore.
-     */
-    public function getPriority();
-}
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index 70bf05170760..645c8ad8d275 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -125,7 +125,7 @@ class ServiceProvider extends AbstractServiceProvider
 
     public static function getConsoleCommandRegistry(ContainerInterface $container): Console\CommandRegistry
     {
-        return new Console\CommandRegistry($container->get(Package\PackageManager::class), $container);
+        return new Console\CommandRegistry($container);
     }
 
     public static function getEventDispatcher(ContainerInterface $container): EventDispatcher\EventDispatcher
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-91473-DeprecatedFunctionalityRemoved.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-91473-DeprecatedFunctionalityRemoved.rst
index a1aa4ed618e6..8402a150e8e6 100644
--- a/typo3/sysext/core/Documentation/Changelog/master/Breaking-91473-DeprecatedFunctionalityRemoved.rst
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-91473-DeprecatedFunctionalityRemoved.rst
@@ -12,6 +12,7 @@ Description
 The following PHP classes that have been previously deprecated for v10 have been removed:
 
 - :php:`\TYPO3\CMS\Backend\Template\DocumentTemplate`
+- :php:`\TYPO3\CMS\Core\Console\CommandRequestHandler`
 - :php:`\TYPO3\CMS\Extbase\Domain\Model\AbstractFileCollection`
 - :php:`\TYPO3\CMS\Extbase\Domain\Model\FileMount`
 - :php:`\TYPO3\CMS\Extbase\Domain\Model\FolderBasedFileCollection`
@@ -26,6 +27,7 @@ The following PHP classes that have been previously deprecated for v10 have been
 
 The following PHP interfaces that have been previously deprecated for v10 have been removed:
 
+- :php:`\TYPO3\CMS\Core\Console\RequestHandlerInterface`
 - :php:`\TYPO3\CMS\Core\Resource\ResourceFactoryInterface`
 - :php:`\TYPO3\CMS\Frontend\ContentObject\ContentObjectGetSingleHookInterface`
 
@@ -40,6 +42,7 @@ The following PHP class aliases that have been previously deprecated for v10 hav
 
 The following PHP class methods that have been previously deprecated for v10 have been removed:
 
+- :php:`\TYPO3\CMS\Core\Console\CommandRegistry->getIterator`
 - :php:`\TYPO3\CMS\Core\DataHandling\DataHandler->assemblePermissions`
 - :php:`\TYPO3\CMS\Core\DataHandling\DataHandler->process_uploads`
 - :php:`\TYPO3\CMS\Core\DataHandling\DataHandler->setTSconfigPermissions`
@@ -300,7 +303,8 @@ The following features are now always enabled:
 
 The following features have been removed:
 
-* All install tool upgrade wizards upgrading from v8 to v9
+- All install tool upgrade wizards upgrading from v8 to v9
+- CLI Command Configuration definition via :file:`Commands.php`
 
 The following database tables have been removed:
 
diff --git a/typo3/sysext/core/Tests/Unit/Console/CommandRegistryTest.php b/typo3/sysext/core/Tests/Unit/Console/CommandRegistryTest.php
index f0e11b9957a5..5927e3b52d78 100644
--- a/typo3/sysext/core/Tests/Unit/Console/CommandRegistryTest.php
+++ b/typo3/sysext/core/Tests/Unit/Console/CommandRegistryTest.php
@@ -17,13 +17,10 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Tests\Unit\Console;
 
-use org\bovigo\vfs\vfsStream;
 use Psr\Container\ContainerInterface;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
 use TYPO3\CMS\Core\Console\CommandRegistry;
-use TYPO3\CMS\Core\Package\PackageInterface;
-use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 /**
@@ -31,16 +28,6 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
  */
 class CommandRegistryTest extends UnitTestCase
 {
-    /**
-     * @var \org\bovigo\vfs\vfsStreamDirectory
-     */
-    protected $rootDirectory;
-
-    /**
-     * @var PackageManager|\Prophecy\Prophecy\ObjectProphecy
-     */
-    protected $packageManagerProphecy;
-
     /**
      * @var ContainerInterface|\Prophecy\Prophecy\ObjectProphecy
      */
@@ -53,10 +40,6 @@ class CommandRegistryTest extends UnitTestCase
     {
         parent::setUp();
 
-        /** @var PackageManager */
-        $this->packageManagerProphecy = $this->prophesize(PackageManager::class);
-        $this->packageManagerProphecy->getActivePackages()->willReturn([]);
-
         /** @var ContainerInterface */
         $this->containerProphecy = $this->prophesize(ContainerInterface::class);
     }
@@ -66,7 +49,7 @@ class CommandRegistryTest extends UnitTestCase
      */
     public function implementsCommandLoaderInterface()
     {
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
+        $commandRegistry = new CommandRegistry($this->containerProphecy->reveal());
         self::assertInstanceof(CommandLoaderInterface::class, $commandRegistry);
     }
 
@@ -81,7 +64,7 @@ class CommandRegistryTest extends UnitTestCase
         $this->containerProphecy->get('command1')->willReturn(new $command1MockClass());
         $this->containerProphecy->get('command2')->willReturn(new $command2MockClass());
 
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
+        $commandRegistry = new CommandRegistry($this->containerProphecy->reveal());
         $commandRegistry->addLazyCommand('test:command', 'command1');
         $commandRegistry->addLazyCommand('test:command2', 'command2');
 
@@ -91,47 +74,4 @@ class CommandRegistryTest extends UnitTestCase
         self::assertInstanceOf($command1MockClass, $commandRegistry->get('test:command'));
         self::assertInstanceOf($command1MockClass, $commandRegistry->get('test:command2'));
     }
-
-    /**
-     * @test
-     */
-    public function iteratesLegacyCommandsOfActivePackages()
-    {
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $commands = iterator_to_array($commandRegistry->getLegacyCommands());
-
-        self::assertCount(0, $commands);
-        self::assertContainsOnlyInstancesOf(Command::class, $commands);
-    }
-
-    /**
-     * @test
-     */
-    public function lazyCommandOverridesLegacyCommandsWithoutDeprecationError()
-    {
-        $commandMockClass = $this->getMockClass(Command::class, ['dummy']);
-        $this->rootDirectory = vfsStream::setup('root', null, [
-            'package1' => [
-                'Configuration' => [
-                    'Commands.php' => '<?php return ["first:command" => [ "class" => "' . $commandMockClass . '" ]];',
-                ],
-            ],
-        ]);
-        /** @var PackageInterface */
-        $package = $this->prophesize(PackageInterface::class);
-        $package->getPackagePath()->willReturn($this->rootDirectory->getChild('package1')->url() . '/');
-
-        $this->packageManagerProphecy->getActivePackages()->willReturn([$package->reveal()]);
-
-        $this->containerProphecy->get($commandMockClass)->willReturn(new $commandMockClass());
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $commandRegistry->addLazyCommand('first:command', $commandMockClass);
-
-        $nonLazyCommands = iterator_to_array($commandRegistry->getLegacyCommands());
-        $lazyCommands = $commandRegistry->getNames();
-
-        self::assertCount(0, $nonLazyCommands);
-        self::assertCount(1, $lazyCommands);
-    }
 }
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Console/CommandRegistryTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Console/CommandRegistryTest.php
deleted file mode 100644
index b6d56d0a0116..000000000000
--- a/typo3/sysext/core/Tests/UnitDeprecated/Console/CommandRegistryTest.php
+++ /dev/null
@@ -1,183 +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\Core\Tests\UnitDeprecated\Console;
-
-use org\bovigo\vfs\vfsStream;
-use Prophecy\Prophecy\ObjectProphecy;
-use Psr\Container\ContainerInterface;
-use Symfony\Component\Console\Command\Command;
-use TYPO3\CMS\Core\Console\CommandNameAlreadyInUseException;
-use TYPO3\CMS\Core\Console\CommandRegistry;
-use TYPO3\CMS\Core\Console\UnknownCommandException;
-use TYPO3\CMS\Core\Package\PackageInterface;
-use TYPO3\CMS\Core\Package\PackageManager;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Testcase for CommandRegistry
- */
-class CommandRegistryTest extends UnitTestCase
-{
-    /**
-     * @var \org\bovigo\vfs\vfsStreamDirectory
-     */
-    protected $rootDirectory;
-
-    /**
-     * @var PackageManager|\Prophecy\Prophecy\ObjectProphecy
-     */
-    protected $packageManagerProphecy;
-
-    /**
-     * @var ContainerInterface|\Prophecy\Prophecy\ObjectProphecy
-     */
-    protected $containerProphecy;
-
-    /**
-     * Set up this testcase
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $commandMockClass = $this->getMockClass(Command::class, ['dummy']);
-        $this->rootDirectory = vfsStream::setup('root', null, [
-            'package1' => [
-                'Configuration' => [
-                    'Commands.php' => '<?php return ["first:command" => [ "class" => "' . $commandMockClass . '" ]];',
-                ],
-            ],
-            'package2' => [
-                'Configuration' => [
-                    'Commands.php' => '<?php return ["second:command" => [ "class" => "' . $commandMockClass . '" ]];',
-                ],
-            ],
-            'package3' => [
-                'Configuration' => [
-                    'Commands.php' => '<?php return ["third:command" => [ "class" => "' . $commandMockClass . '" ]];',
-                ],
-            ],
-            'package4' => [
-                'Configuration' => [
-                    'Commands.php' => '<?php return ["third:command" => [ "class" => "' . $commandMockClass . '" ]];',
-                ],
-            ],
-        ]);
-
-        /** @var PackageManager */
-        $this->packageManagerProphecy = $this->prophesize(PackageManager::class);
-
-        /** @var ContainerInterface */
-        $this->containerProphecy = $this->prophesize(ContainerInterface::class);
-    }
-
-    /**
-     * @test
-     */
-    public function iteratesCommandsOfActivePackages()
-    {
-        /** @var PackageInterface */
-        $package1 = $this->prophesize(PackageInterface::class);
-        $package1->getPackagePath()->willReturn($this->rootDirectory->getChild('package1')->url() . '/');
-        /** @var PackageInterface */
-        $package2 = $this->prophesize(PackageInterface::class);
-        $package2->getPackagePath()->willReturn($this->rootDirectory->getChild('package2')->url() . '/');
-
-        $this->packageManagerProphecy->getActivePackages()->willReturn([$package1->reveal(), $package2->reveal()]);
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $commands = iterator_to_array($commandRegistry);
-
-        self::assertCount(2, $commands);
-        self::assertContainsOnlyInstancesOf(Command::class, $commands);
-    }
-
-    /**
-     * @test
-     */
-    public function iteratesLegacyCommandsOfActivePackages()
-    {
-        /** @var PackageInterface */
-        $package1 = $this->prophesize(PackageInterface::class);
-        $package1->getPackagePath()->willReturn($this->rootDirectory->getChild('package1')->url() . '/');
-        /** @var PackageInterface */
-        $package2 = $this->prophesize(PackageInterface::class);
-        $package2->getPackagePath()->willReturn($this->rootDirectory->getChild('package2')->url() . '/');
-
-        $this->packageManagerProphecy->getActivePackages()->willReturn([$package1->reveal(), $package2->reveal()]);
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $commands = iterator_to_array($commandRegistry->getLegacyCommands());
-
-        self::assertCount(2, $commands);
-        self::assertContainsOnlyInstancesOf(Command::class, $commands);
-    }
-
-    /**
-     * @test
-     */
-    public function throwsExceptionOnDuplicateCommand()
-    {
-        /** @var PackageInterface */
-        $package3 = $this->prophesize(PackageInterface::class);
-        $package3->getPackagePath()->willReturn($this->rootDirectory->getChild('package3')->url() . '/');
-        /** @var PackageInterface */
-        $package4 = $this->prophesize(PackageInterface::class);
-        $package4->getPackagePath()->willReturn($this->rootDirectory->getChild('package4')->url() . '/');
-        $package4->getPackageKey()->willReturn('package4');
-
-        $this->packageManagerProphecy->getActivePackages()->willReturn([$package3->reveal(), $package4->reveal()]);
-
-        $this->expectException(CommandNameAlreadyInUseException::class);
-        $this->expectExceptionCode(1484486383);
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        iterator_to_array($commandRegistry);
-    }
-
-    /**
-     * @test
-     */
-    public function getCommandByIdentifierReturnsRegisteredCommand()
-    {
-        /** @var PackageInterface|ObjectProphecy $package */
-        $package = $this->prophesize(PackageInterface::class);
-        $package->getPackagePath()->willReturn($this->rootDirectory->getChild('package1')->url() . '/');
-        $package->getPackageKey()->willReturn('package1');
-
-        $this->packageManagerProphecy->getActivePackages()->willReturn([$package->reveal()]);
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $command = $commandRegistry->getCommandByIdentifier('first:command');
-
-        self::assertInstanceOf(Command::class, $command);
-    }
-
-    /**
-     * @test
-     */
-    public function throwsUnknownCommandExceptionIfUnregisteredCommandIsRequested()
-    {
-        $this->packageManagerProphecy->getActivePackages()->willReturn([]);
-
-        $this->expectException(UnknownCommandException::class);
-        $this->expectExceptionCode(1510906768);
-
-        $commandRegistry = new CommandRegistry($this->packageManagerProphecy->reveal(), $this->containerProphecy->reveal());
-        $commandRegistry->getCommandByIdentifier('foo');
-    }
-}
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
index d618e0c0d703..e16d601c8c00 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
@@ -1321,11 +1321,13 @@ return [
     'TYPO3\CMS\Core\Console\CommandRequestHandler' => [
         'restFiles' => [
             'Deprecation-88839-CLILowlevelRequestHandlers.rst',
+            'Breaking-91473-DeprecatedFunctionalityRemoved.rst'
         ],
     ],
     'TYPO3\CMS\Core\Console\RequestHandlerInterface' => [
         'restFiles' => [
             'Deprecation-88839-CLILowlevelRequestHandlers.rst',
+            'Breaking-91473-DeprecatedFunctionalityRemoved.rst'
         ],
     ],
     'TYPO3\CMS\Core\Localization\Parser\LocallangXmlParser' => [
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
index 63fe67e585fa..f645e3f64c69 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
@@ -4438,6 +4438,7 @@ return [
         'restFiles' => [
             'Feature-89139-AddDependencyInjectionSupportForConsoleCommands.rst',
             'Deprecation-89139-ConsoleCommandsConfigurationFormatCommandsPhp.rst',
+            'Breaking-91473-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
     'TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->cImage' => [
-- 
GitLab