From ac1b1b278c3f9f6379d7e092208653c2cf5cba3c Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <ben@bnf.dev>
Date: Mon, 30 Oct 2023 17:35:40 +0100
Subject: [PATCH] [TASK] Respect the AsCommand `hidden` constructor argument
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Symfony's AsCommand attribute encodes `hidden = true` into
a trailing `|` for the name attribute. Drop that for our
command list and mark the command as hidden in the command
registry.

Resolves: #102292
Related: #101567
Releases: main, 12.4
Change-Id: I6415e3f5525764aab336bab20432c032e0665632
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81617
Tested-by: Chris Müller <typo3@krue.ml>
Reviewed-by: Garvin Hicking <gh@faktor-e.de>
Tested-by: Garvin Hicking <gh@faktor-e.de>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Chris Müller <typo3@krue.ml>
---
 composer.json                                 |  1 +
 typo3/sysext/core/Configuration/Services.php  |  6 +-
 ...onyAttributeToAutoconfigureCliCommands.rst | 13 ++--
 .../AsCommandAttributeTest.php                | 70 +++++++++++++++++++
 .../Classes/Command/HiddenTestCommand.php     | 32 +++++++++
 .../Classes/Command/VisibleTestCommand.php    | 32 +++++++++
 .../test_di/Configuration/Services.yaml       |  8 +++
 .../Fixtures/Extensions/test_di/composer.json | 19 +++++
 .../Extensions/test_di/ext_emconf.php         | 21 ++++++
 9 files changed, 192 insertions(+), 10 deletions(-)
 create mode 100644 typo3/sysext/core/Tests/Functional/DependencyInjection/AsCommandAttributeTest.php
 create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/HiddenTestCommand.php
 create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/VisibleTestCommand.php
 create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Configuration/Services.yaml
 create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/composer.json
 create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/ext_emconf.php

diff --git a/composer.json b/composer.json
index 6eed577a52e3..c1ed28c71992 100644
--- a/composer.json
+++ b/composer.json
@@ -309,6 +309,7 @@
 			"TYPO3Tests\\TestValidators\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_validators/Classes/",
 			"TYPO3Tests\\TestDatahandler\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler/Classes/",
 			"TYPO3Tests\\TestDataMapper\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/",
+			"TYPO3Tests\\TestDi\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/",
 			"TYPO3Tests\\TestFluidTemplate\\": "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_fluid_template/Classes/",
 			"TYPO3Tests\\TestIrreForeignfield\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_irre_foreignfield/Classes/",
 			"TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/",
diff --git a/typo3/sysext/core/Configuration/Services.php b/typo3/sysext/core/Configuration/Services.php
index c45e9d00a6c0..b3bc50bf4664 100644
--- a/typo3/sysext/core/Configuration/Services.php
+++ b/typo3/sysext/core/Configuration/Services.php
@@ -43,11 +43,11 @@ return static function (ContainerConfigurator $container, ContainerBuilder $cont
             $definition->addTag(
                 'console.command',
                 [
-                    'command' => $attribute->name,
+                    'command' => ltrim($attribute->name, '|'),
                     'description' => $attribute->description,
-                    // `schedulable` and `hidden` flags are not configurable via symfony attribute parameters, use sane defaults
+                    'hidden' => str_starts_with($attribute->name, '|'),
+                    // The `schedulable` flag is not configurable via symfony attribute parameters, use sane defaults
                     'schedulable' => true,
-                    'hidden' => false,
                 ]
             );
         }
diff --git a/typo3/sysext/core/Documentation/Changelog/12.4.x/Important-101567-UseSymfonyAttributeToAutoconfigureCliCommands.rst b/typo3/sysext/core/Documentation/Changelog/12.4.x/Important-101567-UseSymfonyAttributeToAutoconfigureCliCommands.rst
index 5560f34dc28a..f9f588bb8d89 100644
--- a/typo3/sysext/core/Documentation/Changelog/12.4.x/Important-101567-UseSymfonyAttributeToAutoconfigureCliCommands.rst
+++ b/typo3/sysext/core/Documentation/Changelog/12.4.x/Important-101567-UseSymfonyAttributeToAutoconfigureCliCommands.rst
@@ -11,12 +11,12 @@ See :issue:`101567`
 Description
 ===========
 
-The symfony PHP attribute :php:`\Symfony\Component\Console\Attribute\AsCommand` is now accepted to register
-console commands.
-This way CLI commands can be registered by setting the attribute on the command class.
-Only the parameters command, description are still viable.
-In order to overwrite the parameters schedulable and hidden use the old :file:`Services.yaml` way to
-register console commands. By default schedulable is true and hidden is false.
+The symfony PHP attribute :php:`\Symfony\Component\Console\Attribute\AsCommand`
+is now accepted to register console commands.
+This way CLI commands can be registered by setting the attribute on the command
+class. Only the parameters `command`, `description` and `hidden` are still viable. In order to
+overwrite the schedulable parameter use the old :file:`Services.yaml` way to
+register console commands. By default `schedulable` is true.
 
 Before:
 
@@ -29,7 +29,6 @@ Before:
           command: 'myprefix:dofoo'
           description: 'My description'
           schedulable: true
-          hidden: false
 
 After:
 
diff --git a/typo3/sysext/core/Tests/Functional/DependencyInjection/AsCommandAttributeTest.php b/typo3/sysext/core/Tests/Functional/DependencyInjection/AsCommandAttributeTest.php
new file mode 100644
index 000000000000..6bf5a6f4663a
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/DependencyInjection/AsCommandAttributeTest.php
@@ -0,0 +1,70 @@
+<?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\Functional\DependencyInjection;
+
+use TYPO3\CMS\Core\Console\CommandRegistry;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+use TYPO3Tests\TestDi\Command\HiddenTestCommand;
+use TYPO3Tests\TestDi\Command\VisibleTestCommand;
+
+final class AsCommandAttributeTest extends FunctionalTestCase
+{
+    protected array $testExtensionsToLoad = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di',
+    ];
+
+    /**
+     * @test
+     */
+    public function asCommandRegisteredToCommandRegistry(): void
+    {
+        $commandRegistry = $this->get(CommandRegistry::class);
+
+        self::assertTrue($commandRegistry->has('testdi:ascommand:visible'));
+        self::assertInstanceOf(VisibleTestCommand::class, $commandRegistry->get('testdi:ascommand:visible'));
+
+        self::assertTrue($commandRegistry->has('testdi:ascommand:hidden'));
+        self::assertInstanceOf(HiddenTestCommand::class, $commandRegistry->get('testdi:ascommand:hidden'));
+    }
+
+    /**
+     * @test
+     */
+    public function asCommandHiddenAttributeIsRespected(): void
+    {
+        $commandRegistry = $this->get(CommandRegistry::class);
+        $visibleList = $commandRegistry->filter();
+
+        self::assertArrayHasKey('testdi:ascommand:visible', $visibleList);
+        self::assertArrayNotHasKey('testdi:ascommand:hidden', $visibleList);
+    }
+
+    /**
+     * @test
+     */
+    public function asCommandSetsDescription(): void
+    {
+        $commandRegistry = $this->get(CommandRegistry::class);
+
+        $visibleCommand = $commandRegistry->get('testdi:ascommand:visible');
+        $hiddenCommand = $commandRegistry->get('testdi:ascommand:hidden');
+
+        self::assertEquals('This is a visible command.', $visibleCommand->getDescription());
+        self::assertEquals('This is a hidden command.', $hiddenCommand->getDescription());
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/HiddenTestCommand.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/HiddenTestCommand.php
new file mode 100644
index 000000000000..98f51e7fa7e7
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/HiddenTestCommand.php
@@ -0,0 +1,32 @@
+<?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 TYPO3Tests\TestDi\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+#[AsCommand(name: 'testdi:ascommand:hidden', description: 'This is a hidden command.', hidden: true)]
+class HiddenTestCommand extends Command
+{
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        return Command::SUCCESS;
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/VisibleTestCommand.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/VisibleTestCommand.php
new file mode 100644
index 000000000000..c8556e2d857b
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Classes/Command/VisibleTestCommand.php
@@ -0,0 +1,32 @@
+<?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 TYPO3Tests\TestDi\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+#[AsCommand(name: 'testdi:ascommand:visible', description: 'This is a visible command.')]
+class VisibleTestCommand extends Command
+{
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        return Command::SUCCESS;
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Configuration/Services.yaml b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Configuration/Services.yaml
new file mode 100644
index 000000000000..257a0b4ecd91
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/Configuration/Services.yaml
@@ -0,0 +1,8 @@
+services:
+  _defaults:
+    autowire: true
+    autoconfigure: true
+    public: false
+
+  TYPO3Tests\TestDi\:
+    resource: '../Classes/*'
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/composer.json b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/composer.json
new file mode 100644
index 000000000000..5fdaeac5a5c6
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/composer.json
@@ -0,0 +1,19 @@
+{
+	"name": "typo3tests/test-di",
+	"type": "typo3-cms-extension",
+	"description": "This extension contains dependency injection fixtures.",
+	"license": "GPL-2.0-or-later",
+	"require": {
+		"typo3/cms-core": "13.0.*@dev"
+	},
+	"extra": {
+		"typo3/cms": {
+			"extension-key": "test_di"
+		}
+	},
+	"autoload": {
+		"psr-4": {
+			"TYPO3Tests\\TestDi\\": "Classes/"
+		}
+	}
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/ext_emconf.php
new file mode 100644
index 000000000000..c89d491b9310
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_di/ext_emconf.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+$EM_CONF[$_EXTKEY] = [
+    'title' => 'This extension contains dependency injection fixtures.',
+    'description' => 'This extension contains dependency injection fixture.',
+    'category' => 'example',
+    'version' => '13.0.0',
+    'state' => 'beta',
+    'author' => 'Benjamin Franzke',
+    'author_email' => 'ben@bnf.dev',
+    'author_company' => '',
+    'constraints' => [
+        'depends' => [
+            'typo3' => '13.0.0',
+        ],
+        'conflicts' => [],
+        'suggests' => [],
+    ],
+];
-- 
GitLab