diff --git a/Application.php b/Application.php
index f0e0a303e..40a1ac847 100644
--- a/Application.php
+++ b/Application.php
@@ -65,7 +65,7 @@
  * Usage:
  *
  *     $app = new Application('myapp', '1.0 (stable)');
- *     $app->add(new SimpleCommand());
+ *     $app->addCommand(new SimpleCommand());
  *     $app->run();
  *
  * @author Fabien Potencier <fabien@symfony.com>
@@ -512,7 +512,7 @@ public function getLongVersion(): string
      */
     public function register(string $name): Command
     {
-        return $this->add(new Command($name));
+        return $this->addCommand(new Command($name));
     }
 
     /**
@@ -520,12 +520,12 @@ public function register(string $name): Command
      *
      * If a Command is not enabled it will not be added.
      *
-     * @param Command[] $commands An array of commands
+     * @param callable[]|Command[] $commands An array of commands
      */
     public function addCommands(array $commands): void
     {
         foreach ($commands as $command) {
-            $this->add($command);
+            $this->addCommand($command);
         }
     }
 
@@ -535,10 +535,14 @@ public function addCommands(array $commands): void
      * If a command with the same name already exists, it will be overridden.
      * If the command is not enabled it will not be added.
      */
-    public function add(Command $command): ?Command
+    public function addCommand(callable|Command $command): ?Command
     {
         $this->init();
 
+        if (!$command instanceof Command) {
+            $command = new Command(null, $command);
+        }
+
         $command->setApplication($this);
 
         if (!$command->isEnabled()) {
@@ -604,7 +608,7 @@ public function has(string $name): bool
     {
         $this->init();
 
-        return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));
+        return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->addCommand($this->commandLoader->get($name)));
     }
 
     /**
@@ -1322,7 +1326,7 @@ private function init(): void
         $this->initialized = true;
 
         foreach ($this->getDefaultCommands() as $command) {
-            $this->add($command);
+            $this->addCommand($command);
         }
     }
 }
diff --git a/Attribute/Argument.php b/Attribute/Argument.php
index e6a94d2f1..203dcc2af 100644
--- a/Attribute/Argument.php
+++ b/Attribute/Argument.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Console\Completion\CompletionInput;
 use Symfony\Component\Console\Completion\Suggestion;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
 use Symfony\Component\Console\Exception\LogicException;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputInterface;
@@ -27,6 +28,7 @@ class Argument
     private array|\Closure $suggestedValues;
     private ?int $mode = null;
     private string $function = '';
+    private string $typeName = '';
 
     /**
      * Represents a console command <argument> definition.
@@ -66,20 +68,23 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
             throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $name, $self->function));
         }
 
-        $parameterTypeName = $type->getName();
+        $self->typeName = $type->getName();
+        $isBackedEnum = is_subclass_of($self->typeName, \BackedEnum::class);
 
-        if (!\in_array($parameterTypeName, self::ALLOWED_TYPES, true)) {
-            throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command argument. Only "%s" types are allowed.', $parameterTypeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
+        if (!\in_array($self->typeName, self::ALLOWED_TYPES, true) && !$isBackedEnum) {
+            throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command argument. Only "%s" types and backed enums are allowed.', $self->typeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
         }
 
         if (!$self->name) {
             $self->name = (new UnicodeString($name))->kebab();
         }
 
-        $self->default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
+        if ($parameter->isDefaultValueAvailable()) {
+            $self->default = $parameter->getDefaultValue() instanceof \BackedEnum ? $parameter->getDefaultValue()->value : $parameter->getDefaultValue();
+        }
 
         $self->mode = $parameter->isDefaultValueAvailable() || $parameter->allowsNull() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
-        if ('array' === $parameterTypeName) {
+        if ('array' === $self->typeName) {
             $self->mode |= InputArgument::IS_ARRAY;
         }
 
@@ -87,6 +92,10 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
             $self->suggestedValues = [$instance, $self->suggestedValues[1]];
         }
 
+        if ($isBackedEnum && !$self->suggestedValues) {
+            $self->suggestedValues = array_column(($self->typeName)::cases(), 'value');
+        }
+
         return $self;
     }
 
@@ -105,6 +114,12 @@ public function toInputArgument(): InputArgument
      */
     public function resolveValue(InputInterface $input): mixed
     {
-        return $input->getArgument($this->name);
+        $value = $input->getArgument($this->name);
+
+        if (is_subclass_of($this->typeName, \BackedEnum::class) && (\is_string($value) || \is_int($value))) {
+            return ($this->typeName)::tryFrom($value) ?? throw InvalidArgumentException::fromEnumValue($this->name, $value, $this->suggestedValues);
+        }
+
+        return $value;
     }
 }
diff --git a/Attribute/AsCommand.php b/Attribute/AsCommand.php
index 767d46ebb..8b4cea9c8 100644
--- a/Attribute/AsCommand.php
+++ b/Attribute/AsCommand.php
@@ -13,11 +13,9 @@
 
 /**
  * Service tag to autoconfigure commands.
- *
- * @final since Symfony 7.3
  */
 #[\Attribute(\Attribute::TARGET_CLASS)]
-class AsCommand
+final class AsCommand
 {
     /**
      * @param string      $name        The name of the command, used when calling it (i.e. "cache:clear")
@@ -25,6 +23,7 @@ class AsCommand
      * @param string[]    $aliases     The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean")
      * @param bool        $hidden      If true, the command won't be shown when listing all the available commands, but it can still be run as any other command
      * @param string|null $help        The help content of the command, displayed with the help page
+     * @param string[]    $usages      The list of usage examples, displayed with the help page
      */
     public function __construct(
         public string $name,
@@ -32,6 +31,7 @@ public function __construct(
         array $aliases = [],
         bool $hidden = false,
         public ?string $help = null,
+        public array $usages = [],
     ) {
         if (!$hidden && !$aliases) {
             return;
diff --git a/Attribute/Option.php b/Attribute/Option.php
index 788353463..6781e7dbd 100644
--- a/Attribute/Option.php
+++ b/Attribute/Option.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Console\Completion\CompletionInput;
 use Symfony\Component\Console\Completion\Suggestion;
+use Symfony\Component\Console\Exception\InvalidOptionException;
 use Symfony\Component\Console\Exception\LogicException;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
@@ -75,7 +76,7 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
             $self->name = (new UnicodeString($name))->kebab();
         }
 
-        $self->default = $parameter->getDefaultValue();
+        $self->default = $parameter->getDefaultValue() instanceof \BackedEnum ? $parameter->getDefaultValue()->value : $parameter->getDefaultValue();
         $self->allowNull = $parameter->allowsNull();
 
         if ($type instanceof \ReflectionUnionType) {
@@ -87,9 +88,10 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
         }
 
         $self->typeName = $type->getName();
+        $isBackedEnum = is_subclass_of($self->typeName, \BackedEnum::class);
 
-        if (!\in_array($self->typeName, self::ALLOWED_TYPES, true)) {
-            throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types are allowed.', $self->typeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
+        if (!\in_array($self->typeName, self::ALLOWED_TYPES, true) && !$isBackedEnum) {
+            throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types and BackedEnum are allowed.', $self->typeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
         }
 
         if ('bool' === $self->typeName && $self->allowNull && \in_array($self->default, [true, false], true)) {
@@ -115,6 +117,10 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
             $self->suggestedValues = [$instance, $self->suggestedValues[1]];
         }
 
+        if ($isBackedEnum && !$self->suggestedValues) {
+            $self->suggestedValues = array_column(($self->typeName)::cases(), 'value');
+        }
+
         return $self;
     }
 
@@ -140,6 +146,10 @@ public function resolveValue(InputInterface $input): mixed
             return true;
         }
 
+        if (is_subclass_of($this->typeName, \BackedEnum::class) && (\is_string($value) || \is_int($value))) {
+            return ($this->typeName)::tryFrom($value) ?? throw InvalidOptionException::fromEnumValue($this->name, $value, $this->suggestedValues);
+        }
+
         if ('array' === $this->typeName && $this->allowNull && [] === $value) {
             return null;
         }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f3ae3d7d..efa6b75fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,26 @@
 CHANGELOG
 =========
 
+8.0
+---
+
+ * Make `AsCommand` attribute class `final`
+ * Remove methods `Command::getDefaultName()` and `Command::getDefaultDescription()` in favor of the `#[AsCommand]` attribute
+ * Ensure closures set via `Command::setCode()` method have proper parameter and return types
+ * Add method `isSilent()` to `OutputInterface`
+ * Remove deprecated `Symfony\Component\Console\Application::add()` method in favor of `Symfony\Component\Console\Application::addCommand()`
+ * Add argument `$finishedIndicator` to `ProgressIndicator::finish()`
+
+7.4
+---
+
+ * Allow setting aliases and the hidden flag via the command name passed to the constructor
+ * Introduce `Symfony\Component\Console\Application::addCommand()` to simplify using invokable commands when the component is used standalone
+ * Deprecate `Symfony\Component\Console\Application::add()` in favor of `Symfony\Component\Console\Application::addCommand()`
+ * Add `BackedEnum` support with `#[Argument]` and `#[Option]` inputs in invokable commands
+ * Allow Usages to be specified via `#[AsCommand]` attribute.
+ * Allow passing invokable commands to `Symfony\Component\Console\Tester\CommandTester`
+
 7.3
 ---
 
diff --git a/Command/Command.php b/Command/Command.php
index 72a10cf76..780fb1c88 100644
--- a/Command/Command.php
+++ b/Command/Command.php
@@ -54,56 +54,39 @@ class Command implements SignalableCommandInterface
     private array $usages = [];
     private ?HelperSet $helperSet = null;
 
-    /**
-     * @deprecated since Symfony 7.3, use the #[AsCommand] attribute instead
-     */
-    public static function getDefaultName(): ?string
-    {
-        trigger_deprecation('symfony/console', '7.3', 'Method "%s()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', __METHOD__);
-
-        if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) {
-            return $attribute[0]->newInstance()->name;
-        }
-
-        return null;
-    }
-
-    /**
-     * @deprecated since Symfony 7.3, use the #[AsCommand] attribute instead
-     */
-    public static function getDefaultDescription(): ?string
-    {
-        trigger_deprecation('symfony/console', '7.3', 'Method "%s()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', __METHOD__);
-
-        if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) {
-            return $attribute[0]->newInstance()->description;
-        }
-
-        return null;
-    }
-
     /**
      * @param string|null $name The name of the command; passing null means it must be set in configure()
      *
      * @throws LogicException When the command name is empty
      */
-    public function __construct(?string $name = null)
+    public function __construct(?string $name = null, ?callable $code = null)
     {
         $this->definition = new InputDefinition();
 
-        $attribute = ((new \ReflectionClass(static::class))->getAttributes(AsCommand::class)[0] ?? null)?->newInstance();
+        if (null !== $code) {
+            if (!\is_object($code) || $code instanceof \Closure) {
+                throw new InvalidArgumentException(\sprintf('The command must be an instance of "%s" or an invokable object.', self::class));
+            }
 
-        if (null === $name) {
-            if (self::class !== (new \ReflectionMethod($this, 'getDefaultName'))->class) {
-                trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultName()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', static::class);
+            /** @var AsCommand $attribute */
+            $attribute = ((new \ReflectionObject($code))->getAttributes(AsCommand::class)[0] ?? null)?->newInstance()
+                ?? throw new LogicException(\sprintf('The command must use the "%s" attribute.', AsCommand::class));
 
-                $defaultName = static::getDefaultName();
-            } else {
-                $defaultName = $attribute?->name;
+            $this->setName($name ?? $attribute->name)
+                ->setDescription($attribute->description ?? '')
+                ->setHelp($attribute->help ?? '')
+                ->setCode($code);
+
+            foreach ($attribute->usages as $usage) {
+                $this->addUsage($usage);
             }
+
+            return;
         }
 
-        if (null === $name && null !== $name = $defaultName) {
+        $attribute = ((new \ReflectionClass(static::class))->getAttributes(AsCommand::class)[0] ?? null)?->newInstance();
+
+        if (null !== $name ??= $attribute?->name) {
             $aliases = explode('|', $name);
 
             if ('' === $name = array_shift($aliases)) {
@@ -119,21 +102,17 @@ public function __construct(?string $name = null)
         }
 
         if ('' === $this->description) {
-            if (self::class !== (new \ReflectionMethod($this, 'getDefaultDescription'))->class) {
-                trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultDescription()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', static::class);
-
-                $defaultDescription = static::getDefaultDescription();
-            } else {
-                $defaultDescription = $attribute?->description;
-            }
-
-            $this->setDescription($defaultDescription ?? '');
+            $this->setDescription($attribute?->description ?? '');
         }
 
         if ('' === $this->help) {
             $this->setHelp($attribute?->help ?? '');
         }
 
+        foreach ($attribute?->usages ?? [] as $usage) {
+            $this->addUsage($usage);
+        }
+
         if (\is_callable($this) && self::class === (new \ReflectionMethod($this, 'execute'))->getDeclaringClass()->name) {
             $this->code = new InvokableCommand($this, $this(...));
         }
@@ -197,10 +176,8 @@ public function isEnabled(): bool
 
     /**
      * Configures the current command.
-     *
-     * @return void
      */
-    protected function configure()
+    protected function configure(): void
     {
     }
 
@@ -229,10 +206,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
      * This method is executed before the InputDefinition is validated.
      * This means that this is the only place where the command can
      * interactively ask for values of missing required arguments.
-     *
-     * @return void
      */
-    protected function interact(InputInterface $input, OutputInterface $output)
+    protected function interact(InputInterface $input, OutputInterface $output): void
     {
     }
 
@@ -245,10 +220,8 @@ protected function interact(InputInterface $input, OutputInterface $output)
      *
      * @see InputInterface::bind()
      * @see InputInterface::validate()
-     *
-     * @return void
      */
-    protected function initialize(InputInterface $input, OutputInterface $output)
+    protected function initialize(InputInterface $input, OutputInterface $output): void
     {
     }
 
diff --git a/Command/InvokableCommand.php b/Command/InvokableCommand.php
index 72ff407c8..4dfd3980f 100644
--- a/Command/InvokableCommand.php
+++ b/Command/InvokableCommand.php
@@ -33,7 +33,6 @@ class InvokableCommand implements SignalableCommandInterface
     private readonly \Closure $code;
     private readonly ?SignalableCommandInterface $signalableCommand;
     private readonly \ReflectionFunction $reflection;
-    private bool $triggerDeprecations = false;
 
     public function __construct(
         private readonly Command $command,
@@ -52,12 +51,6 @@ public function __invoke(InputInterface $input, OutputInterface $output): int
         $statusCode = ($this->code)(...$this->getParameters($input, $output));
 
         if (!\is_int($statusCode)) {
-            if ($this->triggerDeprecations) {
-                trigger_deprecation('symfony/console', '7.3', \sprintf('Returning a non-integer value from the command "%s" is deprecated and will throw an exception in Symfony 8.0.', $this->command->getName()));
-
-                return 0;
-            }
-
             throw new \TypeError(\sprintf('The command "%s" must return an integer value in the "%s" method, but "%s" was returned.', $this->command->getName(), $this->reflection->getName(), get_debug_type($statusCode)));
         }
 
@@ -87,8 +80,6 @@ private function getClosure(callable $code): \Closure
             return $code(...);
         }
 
-        $this->triggerDeprecations = true;
-
         if (null !== (new \ReflectionFunction($code))->getClosureThis()) {
             return $code;
         }
@@ -124,12 +115,6 @@ private function getParameters(InputInterface $input, OutputInterface $output):
             $type = $parameter->getType();
 
             if (!$type instanceof \ReflectionNamedType) {
-                if ($this->triggerDeprecations) {
-                    trigger_deprecation('symfony/console', '7.3', \sprintf('Omitting the type declaration for the parameter "$%s" is deprecated and will throw an exception in Symfony 8.0.', $parameter->getName()));
-
-                    continue;
-                }
-
                 throw new LogicException(\sprintf('The parameter "$%s" must have a named type. Untyped, Union or Intersection types are not supported.', $parameter->getName()));
             }
 
diff --git a/Debug/CliRequest.php b/Debug/CliRequest.php
index b023db07a..6e2c1012b 100644
--- a/Debug/CliRequest.php
+++ b/Debug/CliRequest.php
@@ -24,7 +24,7 @@ public function __construct(
         public readonly TraceableCommand $command,
     ) {
         parent::__construct(
-            attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'],
+            attributes: ['_controller' => $command->command::class, '_virtual_type' => 'command'],
             server: $_SERVER,
         );
     }
diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php
index 562627f4b..d5d76df03 100644
--- a/DependencyInjection/AddConsoleCommandPass.php
+++ b/DependencyInjection/AddConsoleCommandPass.php
@@ -61,14 +61,7 @@ public function process(ContainerBuilder $container): void
 
             /** @var AsCommand|null $attribute */
             $attribute = ($r->getAttributes(AsCommand::class)[0] ?? null)?->newInstance();
-
-            if (Command::class !== (new \ReflectionMethod($class, 'getDefaultName'))->class) {
-                trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultName()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class);
-
-                $defaultName = $class::getDefaultName();
-            } else {
-                $defaultName = $attribute?->name;
-            }
+            $defaultName = $attribute?->name;
 
             $aliases = str_replace('%', '%%', $tags[0]['command'] ?? $defaultName ?? '');
             $aliases = explode('|', $aliases);
@@ -91,6 +84,7 @@ public function process(ContainerBuilder $container): void
 
             $description = $tags[0]['description'] ?? null;
             $help = $tags[0]['help'] ?? null;
+            $usages = $tags[0]['usages'] ?? null;
 
             unset($tags[0]);
             $lazyCommandMap[$commandName] = $id;
@@ -108,6 +102,7 @@ public function process(ContainerBuilder $container): void
 
                 $description ??= $tag['description'] ?? null;
                 $help ??= $tag['help'] ?? null;
+                $usages ??= $tag['usages'] ?? null;
             }
 
             $definition->addMethodCall('setName', [$commandName]);
@@ -124,17 +119,13 @@ public function process(ContainerBuilder $container): void
                 $definition->addMethodCall('setHelp', [str_replace('%', '%%', $help)]);
             }
 
-            if (!$description) {
-                if (Command::class !== (new \ReflectionMethod($class, 'getDefaultDescription'))->class) {
-                    trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultDescription()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class);
-
-                    $description = $class::getDefaultDescription();
-                } else {
-                    $description = $attribute?->description;
+            if ($usages) {
+                foreach ($usages as $usage) {
+                    $definition->addMethodCall('addUsage', [$usage]);
                 }
             }
 
-            if ($description) {
+            if ($description ??= $attribute?->description) {
                 $definition->addMethodCall('setDescription', [str_replace('%', '%%', $description)]);
 
                 $container->register('.'.$id.'.lazy', LazyCommand::class)
diff --git a/Descriptor/ApplicationDescription.php b/Descriptor/ApplicationDescription.php
index 802d68560..ce778c110 100644
--- a/Descriptor/ApplicationDescription.php
+++ b/Descriptor/ApplicationDescription.php
@@ -85,7 +85,6 @@ private function inspectApplication(): void
         foreach ($this->sortCommands($all) as $namespace => $commands) {
             $names = [];
 
-            /** @var Command $command */
             foreach ($commands as $name => $command) {
                 if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                     continue;
@@ -104,6 +103,9 @@ private function inspectApplication(): void
         }
     }
 
+    /**
+     * @return array<string, array<string, Command>>
+     */
     private function sortCommands(array $commands): array
     {
         $namespacedCommands = [];
diff --git a/Exception/InvalidArgumentException.php b/Exception/InvalidArgumentException.php
index 07cc0b61d..0482244f2 100644
--- a/Exception/InvalidArgumentException.php
+++ b/Exception/InvalidArgumentException.php
@@ -16,4 +16,17 @@
  */
 class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
 {
+    /**
+     * @internal
+     */
+    public static function fromEnumValue(string $name, string $value, array|\Closure $suggestedValues): self
+    {
+        $error = \sprintf('The value "%s" is not valid for the "%s" argument.', $value, $name);
+
+        if (\is_array($suggestedValues)) {
+            $error .= \sprintf(' Supported values are "%s".', implode('", "', $suggestedValues));
+        }
+
+        return new self($error);
+    }
 }
diff --git a/Exception/InvalidOptionException.php b/Exception/InvalidOptionException.php
index 5cf62792e..e59167df1 100644
--- a/Exception/InvalidOptionException.php
+++ b/Exception/InvalidOptionException.php
@@ -18,4 +18,17 @@
  */
 class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
 {
+    /**
+     * @internal
+     */
+    public static function fromEnumValue(string $name, string $value, array|\Closure $suggestedValues): self
+    {
+        $error = \sprintf('The value "%s" is not valid for the "%s" option.', $value, $name);
+
+        if (\is_array($suggestedValues)) {
+            $error .= \sprintf(' Supported values are "%s".', implode('", "', $suggestedValues));
+        }
+
+        return new self($error);
+    }
 }
diff --git a/Helper/ProgressIndicator.php b/Helper/ProgressIndicator.php
index b6bbd0cfa..9dc2a6b87 100644
--- a/Helper/ProgressIndicator.php
+++ b/Helper/ProgressIndicator.php
@@ -128,16 +128,9 @@ public function advance(): void
 
     /**
      * Finish the indicator with message.
-     *
-     * @param ?string $finishedIndicator
      */
-    public function finish(string $message/* , ?string $finishedIndicator = null */): void
+    public function finish(string $message, ?string $finishedIndicator = null): void
     {
-        $finishedIndicator = 1 < \func_num_args() ? func_get_arg(1) : null;
-        if (null !== $finishedIndicator && !\is_string($finishedIndicator)) {
-            throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be of the type string or null, "%s" given.', __METHOD__, get_debug_type($finishedIndicator)));
-        }
-
         if (!$this->started) {
             throw new LogicException('Progress indicator has not yet been started.');
         }
diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php
index 8e1591ec1..da7db4f1f 100644
--- a/Helper/QuestionHelper.php
+++ b/Helper/QuestionHelper.php
@@ -234,7 +234,8 @@ protected function writeError(OutputInterface $output, \Exception $error): void
     /**
      * Autocompletes a question.
      *
-     * @param resource $inputStream
+     * @param resource                  $inputStream
+     * @param callable(string):string[] $autocomplete
      */
     private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
     {
@@ -270,7 +271,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
             // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
             if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
                 shell_exec('stty '.$sttyMode);
-                throw new MissingInputException('Aborted.');
+                throw new MissingInputException('Aborted while asking: '.$question->getQuestion());
             } elseif ("\177" === $c) { // Backspace Character
                 if (0 === $numMatches && 0 !== $i) {
                     --$i;
@@ -576,7 +577,7 @@ private function cloneInputStream($inputStream)
 
         // For seekable and writable streams, add all the same data to the
         // cloned stream and then seek to the same offset.
-        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
+        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'], true)) {
             $offset = ftell($inputStream);
             rewind($inputStream);
             stream_copy_to_stream($inputStream, $cloneStream);
diff --git a/Input/InputOption.php b/Input/InputOption.php
index 25fb91782..4f8e66e5c 100644
--- a/Input/InputOption.php
+++ b/Input/InputOption.php
@@ -79,7 +79,7 @@ public function __construct(
             throw new InvalidArgumentException('An option name cannot be empty.');
         }
 
-        if ('' === $shortcut || [] === $shortcut || false === $shortcut) {
+        if ('' === $shortcut || [] === $shortcut) {
             $shortcut = null;
         }
 
diff --git a/Output/OutputInterface.php b/Output/OutputInterface.php
index 969a3b022..ade17169d 100644
--- a/Output/OutputInterface.php
+++ b/Output/OutputInterface.php
@@ -17,8 +17,6 @@
  * OutputInterface is the interface implemented by all Output classes.
  *
  * @author Fabien Potencier <fabien@symfony.com>
- *
- * @method bool isSilent()
  */
 interface OutputInterface
 {
@@ -64,6 +62,8 @@ public function setVerbosity(int $level): void;
      */
     public function getVerbosity(): int;
 
+    public function isSilent(): bool;
+
     /**
      * Returns whether verbosity is quiet (-q).
      */
diff --git a/Question/Question.php b/Question/Question.php
index 46a60c798..cb65bd674 100644
--- a/Question/Question.php
+++ b/Question/Question.php
@@ -24,8 +24,17 @@ class Question
     private ?int $attempts = null;
     private bool $hidden = false;
     private bool $hiddenFallback = true;
+    /**
+     * @var (\Closure(string):string[])|null
+     */
     private ?\Closure $autocompleterCallback = null;
+    /**
+     * @var (\Closure(mixed):mixed)|null
+     */
     private ?\Closure $validator = null;
+    /**
+     * @var (\Closure(mixed):mixed)|null
+     */
     private ?\Closure $normalizer = null;
     private bool $trimmable = true;
     private bool $multiline = false;
@@ -160,6 +169,8 @@ public function setAutocompleterValues(?iterable $values): static
 
     /**
      * Gets the callback function used for the autocompleter.
+     *
+     * @return (callable(string):string[])|null
      */
     public function getAutocompleterCallback(): ?callable
     {
@@ -171,6 +182,8 @@ public function getAutocompleterCallback(): ?callable
      *
      * The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
      *
+     * @param (callable(string):string[])|null $callback
+     *
      * @return $this
      */
     public function setAutocompleterCallback(?callable $callback): static
@@ -187,6 +200,8 @@ public function setAutocompleterCallback(?callable $callback): static
     /**
      * Sets a validator for the question.
      *
+     * @param (callable(mixed):mixed)|null $validator
+     *
      * @return $this
      */
     public function setValidator(?callable $validator): static
@@ -198,6 +213,8 @@ public function setValidator(?callable $validator): static
 
     /**
      * Gets the validator for the question.
+     *
+     * @return (callable(mixed):mixed)|null
      */
     public function getValidator(): ?callable
     {
@@ -237,7 +254,7 @@ public function getMaxAttempts(): ?int
     /**
      * Sets a normalizer for the response.
      *
-     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
+     * @param callable(mixed):mixed $normalizer
      *
      * @return $this
      */
@@ -251,7 +268,7 @@ public function setNormalizer(callable $normalizer): static
     /**
      * Gets the normalizer for the response.
      *
-     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
+     * @return (callable(mixed):mixed)|null
      */
     public function getNormalizer(): ?callable
     {
diff --git a/SingleCommandApplication.php b/SingleCommandApplication.php
index 2b54fb870..837948d12 100644
--- a/SingleCommandApplication.php
+++ b/SingleCommandApplication.php
@@ -57,7 +57,7 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu
         $application->setAutoExit($this->autoExit);
         // Fix the usage of the command displayed with "--help"
         $this->setName($_SERVER['argv'][0]);
-        $application->add($this);
+        $application->addCommand($this);
         $application->setDefaultCommand($this->getName(), true);
 
         $this->running = true;
diff --git a/Style/OutputStyle.php b/Style/OutputStyle.php
index 89a3a4177..9404fedeb 100644
--- a/Style/OutputStyle.php
+++ b/Style/OutputStyle.php
@@ -80,8 +80,7 @@ public function getFormatter(): OutputFormatterInterface
 
     public function isSilent(): bool
     {
-        // @deprecated since Symfony 7.2, change to $this->output->isSilent() in 8.0
-        return method_exists($this->output, 'isSilent') ? $this->output->isSilent() : self::VERBOSITY_SILENT === $this->output->getVerbosity();
+        return $this->output->isSilent();
     }
 
     public function isQuiet(): bool
diff --git a/Style/StyleInterface.php b/Style/StyleInterface.php
index fcc5bc775..1a2232324 100644
--- a/Style/StyleInterface.php
+++ b/Style/StyleInterface.php
@@ -70,11 +70,15 @@ public function table(array $headers, array $rows): void;
 
     /**
      * Asks a question.
+     *
+     * @param (callable(mixed):mixed)|null $validator
      */
     public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed;
 
     /**
      * Asks a question with the user input hidden.
+     *
+     * @param (callable(mixed):mixed)|null $validator
      */
     public function askHidden(string $question, ?callable $validator = null): mixed;
 
diff --git a/Tester/CommandTester.php b/Tester/CommandTester.php
index d39cde7f6..714d88ad5 100644
--- a/Tester/CommandTester.php
+++ b/Tester/CommandTester.php
@@ -24,9 +24,12 @@ class CommandTester
 {
     use TesterTrait;
 
+    private Command $command;
+
     public function __construct(
-        private Command $command,
+        callable|Command $command,
     ) {
+        $this->command = $command instanceof Command ? $command : new Command(null, $command);
     }
 
     /**
diff --git a/Tests/ApplicationTest.php b/Tests/ApplicationTest.php
index 268f8ba50..684ebeb91 100644
--- a/Tests/ApplicationTest.php
+++ b/Tests/ApplicationTest.php
@@ -11,11 +11,16 @@
 
 namespace Symfony\Component\Console\Tests;
 
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
+use PHPUnit\Framework\Attributes\TestWith;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Attribute\AsCommand;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Command\HelpCommand;
+use Symfony\Component\Console\Command\InvokableCommand;
 use Symfony\Component\Console\Command\LazyCommand;
 use Symfony\Component\Console\Command\SignalableCommandInterface;
 use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
@@ -28,6 +33,8 @@
 use Symfony\Component\Console\Event\ConsoleSignalEvent;
 use Symfony\Component\Console\Event\ConsoleTerminateEvent;
 use Symfony\Component\Console\Exception\CommandNotFoundException;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\LogicException;
 use Symfony\Component\Console\Exception\NamespaceNotFoundException;
 use Symfony\Component\Console\Helper\FormatterHelper;
 use Symfony\Component\Console\Helper\HelperSet;
@@ -45,6 +52,8 @@
 use Symfony\Component\Console\SignalRegistry\SignalRegistry;
 use Symfony\Component\Console\Terminal;
 use Symfony\Component\Console\Tester\ApplicationTester;
+use Symfony\Component\Console\Tests\Fixtures\InvokableExtendingCommandTestCommand;
+use Symfony\Component\Console\Tests\Fixtures\InvokableTestCommand;
 use Symfony\Component\Console\Tests\Fixtures\MockableAppliationWithTerminalWidth;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -163,7 +172,7 @@ public function testAll()
         $commands = $application->all();
         $this->assertInstanceOf(HelpCommand::class, $commands['help'], '->all() returns the registered commands');
 
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $commands = $application->all('foo');
         $this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
     }
@@ -174,7 +183,7 @@ public function testAllWithCommandLoader()
         $commands = $application->all();
         $this->assertInstanceOf(HelpCommand::class, $commands['help'], '->all() returns the registered commands');
 
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $commands = $application->all('foo');
         $this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
 
@@ -218,12 +227,12 @@ public function testRegisterAmbiguous()
         $this->assertStringContainsString('It works!', $tester->getDisplay(true));
     }
 
-    public function testAdd()
+    public function testAddCommand()
     {
         $application = new Application();
-        $application->add($foo = new \FooCommand());
+        $application->addCommand($foo = new \FooCommand());
         $commands = $application->all();
-        $this->assertEquals($foo, $commands['foo:bar'], '->add() registers a command');
+        $this->assertEquals($foo, $commands['foo:bar'], '->addCommand() registers a command');
 
         $application = new Application();
         $application->addCommands([$foo = new \FooCommand(), $foo1 = new \Foo1Command()]);
@@ -236,7 +245,58 @@ public function testAddCommandWithEmptyConstructor()
         $this->expectException(\LogicException::class);
         $this->expectExceptionMessage('Command class "Foo5Command" is not correctly initialized. You probably forgot to call the parent constructor.');
 
-        (new Application())->add(new \Foo5Command());
+        (new Application())->addCommand(new \Foo5Command());
+    }
+
+    public function testAddCommandWithExtendedCommand()
+    {
+        $application = new Application();
+        $application->addCommand($foo = new \FooCommand());
+        $commands = $application->all();
+
+        $this->assertEquals($foo, $commands['foo:bar']);
+    }
+
+    public function testAddCommandWithInvokableCommand()
+    {
+        $application = new Application();
+        $application->addCommand($foo = new InvokableTestCommand());
+        $commands = $application->all();
+
+        $this->assertInstanceOf(Command::class, $command = $commands['invokable:test']);
+        $this->assertEquals(new InvokableCommand($command, $foo), (new \ReflectionObject($command))->getProperty('code')->getValue($command));
+    }
+
+    public function testAddCommandWithInvokableExtendedCommand()
+    {
+        $application = new Application();
+        $application->addCommand($foo = new InvokableExtendingCommandTestCommand());
+        $commands = $application->all();
+
+        $this->assertEquals($foo, $commands['invokable:test']);
+    }
+
+    #[DataProvider('provideInvalidInvokableCommands')]
+    public function testAddCommandThrowsExceptionOnInvalidCommand(callable $command, string $expectedException, string $expectedExceptionMessage)
+    {
+        $application = new Application();
+
+        $this->expectException($expectedException);
+        $this->expectExceptionMessage($expectedExceptionMessage);
+
+        $application->addCommand($command);
+    }
+
+    public static function provideInvalidInvokableCommands(): iterable
+    {
+        yield 'a function' => ['strlen', InvalidArgumentException::class, \sprintf('The command must be an instance of "%s" or an invokable object.', Command::class)];
+        yield 'a closure' => [function () {
+        }, InvalidArgumentException::class, \sprintf('The command must be an instance of "%s" or an invokable object.', Command::class)];
+        yield 'without the #[AsCommand] attribute' => [new class {
+            public function __invoke()
+            {
+            }
+        }, LogicException::class, \sprintf('The command must use the "%s" attribute.', AsCommand::class)];
     }
 
     public function testHasGet()
@@ -245,13 +305,13 @@ public function testHasGet()
         $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered');
         $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered');
 
-        $application->add($foo = new \FooCommand());
+        $application->addCommand($foo = new \FooCommand());
         $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered');
         $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
         $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
 
         $application = new Application();
-        $application->add($foo = new \FooCommand());
+        $application->addCommand($foo = new \FooCommand());
         // simulate --help
         $r = new \ReflectionObject($application);
         $p = $r->getProperty('wantHelps');
@@ -266,7 +326,7 @@ public function testHasGetWithCommandLoader()
         $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered');
         $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered');
 
-        $application->add($foo = new \FooCommand());
+        $application->addCommand($foo = new \FooCommand());
         $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered');
         $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
         $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
@@ -307,35 +367,35 @@ public function testGetInvalidCommand()
     public function testGetNamespaces()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
         $this->assertEquals(['foo'], $application->getNamespaces(), '->getNamespaces() returns an array of unique used namespaces');
     }
 
     public function testFindNamespace()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists');
         $this->assertEquals('foo', $application->findNamespace('f'), '->findNamespace() finds a namespace given an abbreviation');
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \Foo2Command());
         $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists');
     }
 
     public function testFindNamespaceWithSubnamespaces()
     {
         $application = new Application();
-        $application->add(new \FooSubnamespaced1Command());
-        $application->add(new \FooSubnamespaced2Command());
+        $application->addCommand(new \FooSubnamespaced1Command());
+        $application->addCommand(new \FooSubnamespaced2Command());
         $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns commands even if the commands are only contained in subnamespaces');
     }
 
     public function testFindAmbiguousNamespace()
     {
         $application = new Application();
-        $application->add(new \BarBucCommand());
-        $application->add(new \FooCommand());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \BarBucCommand());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo2Command());
 
         $expectedMsg = "The namespace \"f\" is ambiguous.\nDid you mean one of these?\n    foo\n    foo1";
 
@@ -348,8 +408,8 @@ public function testFindAmbiguousNamespace()
     public function testFindNonAmbiguous()
     {
         $application = new Application();
-        $application->add(new \TestAmbiguousCommandRegistering());
-        $application->add(new \TestAmbiguousCommandRegistering2());
+        $application->addCommand(new \TestAmbiguousCommandRegistering());
+        $application->addCommand(new \TestAmbiguousCommandRegistering2());
         $this->assertEquals('test-ambiguous', $application->find('test')->getName());
     }
 
@@ -364,9 +424,9 @@ public function testFindInvalidNamespace()
     public function testFindUniqueNameButNamespaceName()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
 
         $this->expectException(CommandNotFoundException::class);
         $this->expectExceptionMessage('Command "foo1" is not defined');
@@ -377,7 +437,7 @@ public function testFindUniqueNameButNamespaceName()
     public function testFind()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
 
         $this->assertInstanceOf(\FooCommand::class, $application->find('foo:bar'), '->find() returns a command if its name exists');
         $this->assertInstanceOf(HelpCommand::class, $application->find('h'), '->find() returns a command if its name exists');
@@ -389,8 +449,8 @@ public function testFind()
     public function testFindCaseSensitiveFirst()
     {
         $application = new Application();
-        $application->add(new \FooSameCaseUppercaseCommand());
-        $application->add(new \FooSameCaseLowercaseCommand());
+        $application->addCommand(new \FooSameCaseUppercaseCommand());
+        $application->addCommand(new \FooSameCaseLowercaseCommand());
 
         $this->assertInstanceOf(\FooSameCaseUppercaseCommand::class, $application->find('f:B'), '->find() returns a command if the abbreviation is the correct case');
         $this->assertInstanceOf(\FooSameCaseUppercaseCommand::class, $application->find('f:BAR'), '->find() returns a command if the abbreviation is the correct case');
@@ -401,7 +461,7 @@ public function testFindCaseSensitiveFirst()
     public function testFindCaseInsensitiveAsFallback()
     {
         $application = new Application();
-        $application->add(new \FooSameCaseLowercaseCommand());
+        $application->addCommand(new \FooSameCaseLowercaseCommand());
 
         $this->assertInstanceOf(\FooSameCaseLowercaseCommand::class, $application->find('f:b'), '->find() returns a command if the abbreviation is the correct case');
         $this->assertInstanceOf(\FooSameCaseLowercaseCommand::class, $application->find('f:B'), '->find() will fallback to case insensitivity');
@@ -411,8 +471,8 @@ public function testFindCaseInsensitiveAsFallback()
     public function testFindCaseInsensitiveSuggestions()
     {
         $application = new Application();
-        $application->add(new \FooSameCaseLowercaseCommand());
-        $application->add(new \FooSameCaseUppercaseCommand());
+        $application->addCommand(new \FooSameCaseLowercaseCommand());
+        $application->addCommand(new \FooSameCaseUppercaseCommand());
 
         $this->expectException(CommandNotFoundException::class);
         $this->expectExceptionMessage('Command "FoO:BaR" is ambiguous');
@@ -434,9 +494,7 @@ public function testFindWithCommandLoader()
         $this->assertInstanceOf(\FooCommand::class, $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
     }
 
-    /**
-     * @dataProvider provideAmbiguousAbbreviations
-     */
+    #[DataProvider('provideAmbiguousAbbreviations')]
     public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage)
     {
         putenv('COLUMNS=120');
@@ -444,9 +502,9 @@ public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExcep
         $this->expectExceptionMessage($expectedExceptionMessage);
 
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
 
         $application->find($abbreviation);
     }
@@ -476,8 +534,8 @@ public function testFindWithAmbiguousAbbreviationsFindsCommandIfAlternativesAreH
     {
         $application = new Application();
 
-        $application->add(new \FooCommand());
-        $application->add(new \FooHiddenCommand());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \FooHiddenCommand());
 
         $this->assertInstanceOf(\FooCommand::class, $application->find('foo:'));
     }
@@ -485,8 +543,8 @@ public function testFindWithAmbiguousAbbreviationsFindsCommandIfAlternativesAreH
     public function testFindCommandEqualNamespace()
     {
         $application = new Application();
-        $application->add(new \Foo3Command());
-        $application->add(new \Foo4Command());
+        $application->addCommand(new \Foo3Command());
+        $application->addCommand(new \Foo4Command());
 
         $this->assertInstanceOf(\Foo3Command::class, $application->find('foo3:bar'), '->find() returns the good command even if a namespace has same name');
         $this->assertInstanceOf(\Foo4Command::class, $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name');
@@ -495,8 +553,8 @@ public function testFindCommandEqualNamespace()
     public function testFindCommandWithAmbiguousNamespacesButUniqueName()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \FoobarCommand());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \FoobarCommand());
 
         $this->assertInstanceOf(\FoobarCommand::class, $application->find('f:f'));
     }
@@ -504,18 +562,16 @@ public function testFindCommandWithAmbiguousNamespacesButUniqueName()
     public function testFindCommandWithMissingNamespace()
     {
         $application = new Application();
-        $application->add(new \Foo4Command());
+        $application->addCommand(new \Foo4Command());
 
         $this->assertInstanceOf(\Foo4Command::class, $application->find('f::t'));
     }
 
-    /**
-     * @dataProvider provideInvalidCommandNamesSingle
-     */
+    #[DataProvider('provideInvalidCommandNamesSingle')]
     public function testFindAlternativeExceptionMessageSingle($name)
     {
         $application = new Application();
-        $application->add(new \Foo3Command());
+        $application->addCommand(new \Foo3Command());
 
         $this->expectException(CommandNotFoundException::class);
         $this->expectExceptionMessage('Did you mean this');
@@ -526,7 +582,7 @@ public function testFindAlternativeExceptionMessageSingle($name)
     public function testDontRunAlternativeNamespaceName()
     {
         $application = new Application();
-        $application->add(new \Foo1Command());
+        $application->addCommand(new \Foo1Command());
         $application->setAutoExit(false);
         $tester = new ApplicationTester($application);
         $tester->run(['command' => 'foos:bar1'], ['decorated' => false]);
@@ -536,7 +592,7 @@ public function testDontRunAlternativeNamespaceName()
     public function testCanRunAlternativeCommandName()
     {
         $application = new Application();
-        $application->add(new \FooWithoutAliasCommand());
+        $application->addCommand(new \FooWithoutAliasCommand());
         $application->setAutoExit(false);
         $tester = new ApplicationTester($application);
         $tester->setInputs(['y']);
@@ -550,7 +606,7 @@ public function testCanRunAlternativeCommandName()
     public function testDontRunAlternativeCommandName()
     {
         $application = new Application();
-        $application->add(new \FooWithoutAliasCommand());
+        $application->addCommand(new \FooWithoutAliasCommand());
         $application->setAutoExit(false);
         $tester = new ApplicationTester($application);
         $tester->setInputs(['n']);
@@ -574,9 +630,9 @@ public function testRunNamespace()
         putenv('COLUMNS=120');
         $application = new Application();
         $application->setAutoExit(false);
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
         $tester = new ApplicationTester($application);
         $tester->run(['command' => 'foo'], ['decorated' => false]);
         $display = trim($tester->getDisplay(true));
@@ -589,9 +645,9 @@ public function testFindAlternativeExceptionMessageMultiple()
     {
         putenv('COLUMNS=120');
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
 
         // Command + plural
         try {
@@ -614,8 +670,8 @@ public function testFindAlternativeExceptionMessageMultiple()
             $this->assertMatchesRegularExpression('/foo1/', $e->getMessage());
         }
 
-        $application->add(new \Foo3Command());
-        $application->add(new \Foo4Command());
+        $application->addCommand(new \Foo3Command());
+        $application->addCommand(new \Foo4Command());
 
         // Subnamespace + plural
         try {
@@ -632,9 +688,9 @@ public function testFindAlternativeCommands()
     {
         $application = new Application();
 
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
 
         try {
             $application->find($commandName = 'Unknown command');
@@ -669,7 +725,7 @@ public function testFindAlternativeCommandsWithAnAlias()
         $application->setCommandLoader(new FactoryCommandLoader([
             'foo3' => static fn () => $fooCommand,
         ]));
-        $application->add($fooCommand);
+        $application->addCommand($fooCommand);
 
         $result = $application->find('foo');
 
@@ -680,10 +736,10 @@ public function testFindAlternativeNamespace()
     {
         $application = new Application();
 
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
-        $application->add(new \Foo3Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
+        $application->addCommand(new \Foo3Command());
 
         try {
             $application->find('Unknown-namespace:Unknown-command');
@@ -715,11 +771,11 @@ public function testFindAlternativesOutput()
     {
         $application = new Application();
 
-        $application->add(new \FooCommand());
-        $application->add(new \Foo1Command());
-        $application->add(new \Foo2Command());
-        $application->add(new \Foo3Command());
-        $application->add(new \FooHiddenCommand());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo1Command());
+        $application->addCommand(new \Foo2Command());
+        $application->addCommand(new \Foo3Command());
+        $application->addCommand(new \FooHiddenCommand());
 
         $expectedAlternatives = [
             'afoobar',
@@ -755,8 +811,8 @@ public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces()
     public function testFindWithDoubleColonInNameThrowsException()
     {
         $application = new Application();
-        $application->add(new \FooCommand());
-        $application->add(new \Foo4Command());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \Foo4Command());
 
         $this->expectException(CommandNotFoundException::class);
         $this->expectExceptionMessage('Command "foo::bar" is not defined.');
@@ -767,7 +823,7 @@ public function testFindWithDoubleColonInNameThrowsException()
     public function testFindHiddenWithExactName()
     {
         $application = new Application();
-        $application->add(new \FooHiddenCommand());
+        $application->addCommand(new \FooHiddenCommand());
 
         $this->assertInstanceOf(\FooHiddenCommand::class, $application->find('foo:hidden'));
         $this->assertInstanceOf(\FooHiddenCommand::class, $application->find('afoohidden'));
@@ -777,16 +833,14 @@ public function testFindAmbiguousCommandsIfAllAlternativesAreHidden()
     {
         $application = new Application();
 
-        $application->add(new \FooCommand());
-        $application->add(new \FooHiddenCommand());
+        $application->addCommand(new \FooCommand());
+        $application->addCommand(new \FooHiddenCommand());
 
         $this->assertInstanceOf(\FooCommand::class, $application->find('foo:'));
     }
 
-    /**
-     * @testWith [true]
-     *           [false]
-     */
+    #[TestWith([true])]
+    #[TestWith([false])]
     public function testSetCatchExceptions(bool $catchErrors)
     {
         $application = new Application();
@@ -815,23 +869,21 @@ public function testSetCatchExceptions(bool $catchErrors)
         }
     }
 
-    /**
-     * @testWith [true]
-     *           [false]
-     */
+    #[TestWith([true])]
+    #[TestWith([false])]
     public function testSetCatchErrors(bool $catchExceptions)
     {
         $application = new Application();
         $application->setAutoExit(false);
         $application->setCatchExceptions($catchExceptions);
-        $application->add((new Command('boom'))->setCode(fn () => throw new \Error('This is an error.')));
+        $application->addCommand((new Command('boom'))->setCode(fn () => throw new \Error('This is an error.')));
 
         putenv('COLUMNS=120');
         $tester = new ApplicationTester($application);
 
         try {
             $tester->run(['command' => 'boom']);
-            $this->fail('The exception is not catched.');
+            $this->fail('The exception is not caught.');
         } catch (\Throwable $e) {
             $this->assertInstanceOf(\Error::class, $e);
             $this->assertSame('This is an error.', $e->getMessage());
@@ -870,7 +922,7 @@ public function testRenderException()
         $tester->run(['command' => 'list', '--foo' => true], ['decorated' => false, 'capture_stderr_separately' => true]);
         $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getErrorOutput(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command');
 
-        $application->add(new \Foo3Command());
+        $application->addCommand(new \Foo3Command());
         $tester = new ApplicationTester($application);
         $tester->run(['command' => 'foo3:bar'], ['decorated' => false, 'capture_stderr_separately' => true]);
         $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
@@ -957,9 +1009,7 @@ public function testRenderExceptionLineBreaks()
         $this->assertStringMatchesFormatFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks');
     }
 
-    /**
-     * @group transient-on-windows
-     */
+    #[Group('transient-on-windows')]
     public function testRenderAnonymousException()
     {
         $application = new Application();
@@ -983,9 +1033,7 @@ public function testRenderAnonymousException()
         $this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
     }
 
-    /**
-     * @group transient-on-windows
-     */
+    #[Group('transient-on-windows')]
     public function testRenderExceptionStackTraceContainsRootException()
     {
         $application = new Application();
@@ -1031,7 +1079,7 @@ public function testRun()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setCatchExceptions(false);
-        $application->add($command = new \Foo1Command());
+        $application->addCommand($command = new \Foo1Command());
         $_SERVER['argv'] = ['cli.php', 'foo:bar1'];
 
         ob_start();
@@ -1116,7 +1164,7 @@ public function testRun()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setCatchExceptions(false);
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $tester = new ApplicationTester($application);
 
         $tester->run(['command' => 'foo:bar', '--no-interaction' => true], ['decorated' => false]);
@@ -1151,7 +1199,7 @@ public function testVerboseValueNotBreakArguments()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setCatchExceptions(false);
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
 
         $output = new StreamOutput(fopen('php://memory', 'w', false));
 
@@ -1244,10 +1292,8 @@ public function testRunDispatchesExitCodeOneForExceptionCodeZero()
         $this->assertTrue($passedRightValue, '-> exit code 1 was passed in the console.terminate event');
     }
 
-    /**
-     * @testWith [-1]
-     *           [-32000]
-     */
+    #[TestWith([-1])]
+    #[TestWith([-32000])]
     public function testRunReturnsExitCodeOneForNegativeExceptionCode($exceptionCode)
     {
         $exception = new \Exception('', $exceptionCode);
@@ -1291,9 +1337,7 @@ public function testAddingOptionWithDuplicateShortcut()
         $application->run($input, $output);
     }
 
-    /**
-     * @dataProvider getAddingAlreadySetDefinitionElementData
-     */
+    #[DataProvider('getAddingAlreadySetDefinitionElementData')]
     public function testAddingAlreadySetDefinitionElementData($def)
     {
         $application = new Application();
@@ -1762,7 +1806,7 @@ public function testSetRunCustomDefaultCommand()
 
         $application = new Application();
         $application->setAutoExit(false);
-        $application->add($command);
+        $application->addCommand($command);
         $application->setDefaultCommand($command->getName());
 
         $tester = new ApplicationTester($application);
@@ -1784,7 +1828,7 @@ public function testSetRunCustomDefaultCommandWithOption()
 
         $application = new Application();
         $application->setAutoExit(false);
-        $application->add($command);
+        $application->addCommand($command);
         $application->setDefaultCommand($command->getName());
 
         $tester = new ApplicationTester($application);
@@ -1799,7 +1843,7 @@ public function testSetRunCustomSingleCommand()
 
         $application = new Application();
         $application->setAutoExit(false);
-        $application->add($command);
+        $application->addCommand($command);
         $application->setDefaultCommand($command->getName(), true);
 
         $tester = new ApplicationTester($application);
@@ -1981,9 +2025,7 @@ public function testCommandNameMismatchWithCommandLoaderKeyThrows()
         $app->get('test');
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalListenerNotCalledByDefault()
     {
         $command = new SignableCommand(false);
@@ -2001,9 +2043,7 @@ public function testSignalListenerNotCalledByDefault()
         $this->assertFalse($dispatcherCalled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalListener()
     {
         $command = new SignableCommand();
@@ -2022,9 +2062,7 @@ public function testSignalListener()
         $this->assertTrue($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalSubscriberNotCalledByDefault()
     {
         $command = new BaseSignableCommand(false);
@@ -2039,9 +2077,7 @@ public function testSignalSubscriberNotCalledByDefault()
         $this->assertFalse($subscriber->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalSubscriber()
     {
         $command = new BaseSignableCommand();
@@ -2060,9 +2096,7 @@ public function testSignalSubscriber()
         $this->assertTrue($subscriber2->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalDispatchWithoutEventToDispatch()
     {
         $command = new SignableCommand();
@@ -2074,9 +2108,7 @@ public function testSignalDispatchWithoutEventToDispatch()
         $this->assertTrue($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalDispatchWithoutEventDispatcher()
     {
         $command = new SignableCommand();
@@ -2088,9 +2120,7 @@ public function testSignalDispatchWithoutEventDispatcher()
         $this->assertTrue($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSetSignalsToDispatchEvent()
     {
         if (!\defined('SIGUSR1')) {
@@ -2150,7 +2180,7 @@ public function testSignalableCommandInterfaceWithoutSignals()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setDispatcher($dispatcher);
-        $application->add($command);
+        $application->addCommand($command);
         $this->assertSame(0, $application->run(new ArrayInput(['signal'])));
     }
 
@@ -2186,7 +2216,7 @@ public function testSignalableCommandDoesNotInterruptedOnTermSignals()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setDispatcher($dispatcher);
-        $application->add($command);
+        $application->addCommand($command);
 
         $this->assertSame(129, $application->run(new ArrayInput(['signal'])));
     }
@@ -2208,7 +2238,7 @@ public function testSignalableWithEventCommandDoesNotInterruptedOnTermSignals()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setDispatcher($dispatcher);
-        $application->add($command);
+        $application->addCommand($command);
         $tester = new ApplicationTester($application);
         $this->assertSame(51, $tester->run(['signal']));
         $expected = <<<EOTXT
@@ -2222,9 +2252,7 @@ public function testSignalableWithEventCommandDoesNotInterruptedOnTermSignals()
         $this->assertTrue($terminateEventDispatched);
     }
 
-    /**
-     * @group tty
-     */
+    #[Group('tty')]
     public function testSignalableRestoresStty()
     {
         if (!Terminal::hasSttyAvailable()) {
@@ -2255,9 +2283,7 @@ public function testSignalableRestoresStty()
         $this->assertSame($previousSttyMode, $sttyMode);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalableInvokableCommand()
     {
         $command = new Command();
@@ -2273,9 +2299,7 @@ public function testSignalableInvokableCommand()
         $this->assertTrue($invokable->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalableInvokableCommandThatExtendsBaseCommand()
     {
         $command = new class extends Command implements SignalableCommandInterface {
@@ -2290,9 +2314,7 @@ public function testSignalableInvokableCommandThatExtendsBaseCommand()
         $this->assertTrue($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmSubscriberNotCalledByDefault()
     {
         $command = new BaseSignableCommand(false);
@@ -2308,9 +2330,7 @@ public function testAlarmSubscriberNotCalledByDefault()
         $this->assertFalse($subscriber->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmSubscriberNotCalledForOtherSignals()
     {
         $command = new SignableCommand();
@@ -2329,9 +2349,7 @@ public function testAlarmSubscriberNotCalledForOtherSignals()
         $this->assertFalse($subscriber2->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmSubscriber()
     {
         $command = new BaseSignableCommand(signal: \SIGALRM);
@@ -2350,9 +2368,7 @@ public function testAlarmSubscriber()
         $this->assertTrue($subscriber2->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmDispatchWithoutEventDispatcher()
     {
         $command = new AlarmableCommand(1);
@@ -2365,9 +2381,7 @@ public function testAlarmDispatchWithoutEventDispatcher()
         $this->assertTrue($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmableCommandWithoutInterval()
     {
         $command = new AlarmableCommand(0);
@@ -2378,15 +2392,13 @@ public function testAlarmableCommandWithoutInterval()
         $application = new Application();
         $application->setAutoExit(false);
         $application->setDispatcher($dispatcher);
-        $application->add($command);
+        $application->addCommand($command);
 
         $this->assertSame(0, $application->run(new ArrayInput(['alarm'])));
         $this->assertFalse($command->signaled);
     }
 
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testAlarmableCommandHandlerCalledAfterEventListener()
     {
         $command = new AlarmableCommand(1);
@@ -2403,12 +2415,9 @@ public function testAlarmableCommandHandlerCalledAfterEventListener()
         $this->assertSame([AlarmEventSubscriber::class, AlarmableCommand::class], $command->signalHandlers);
     }
 
-    /**
-     * @requires extension pcntl
-     *
-     * @testWith [false]
-     *           [4]
-     */
+    #[RequiresPhpExtension('pcntl')]
+    #[TestWith([false])]
+    #[TestWith([4])]
     public function testAlarmSubscriberCalledAfterSignalSubscriberAndInheritsExitCode(int|false $exitCode)
     {
         $command = new BaseSignableCommand(signal: \SIGALRM);
@@ -2459,7 +2468,7 @@ private function createSignalableApplication(Command $command, ?EventDispatcherI
         if ($dispatcher) {
             $application->setDispatcher($dispatcher);
         }
-        $application->add(new LazyCommand($command->getName(), [], '', false, fn () => $command, true));
+        $application->addCommand(new LazyCommand($command->getName(), [], '', false, fn () => $command, true));
 
         return $application;
     }
@@ -2491,7 +2500,7 @@ public function __construct()
         parent::__construct();
 
         $command = new \FooCommand();
-        $this->add($command);
+        $this->addCommand($command);
         $this->setDefaultCommand($command->getName());
     }
 }
diff --git a/Tests/CI/GithubActionReporterTest.php b/Tests/CI/GithubActionReporterTest.php
index a35927950..f4ee70eaf 100644
--- a/Tests/CI/GithubActionReporterTest.php
+++ b/Tests/CI/GithubActionReporterTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\CI;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\CI\GithubActionReporter;
 use Symfony\Component\Console\Output\BufferedOutput;
@@ -31,9 +32,7 @@ public function testIsGithubActionEnvironment()
         }
     }
 
-    /**
-     * @dataProvider annotationsFormatProvider
-     */
+    #[DataProvider('annotationsFormatProvider')]
     public function testAnnotationsFormat(string $type, string $message, ?string $file, ?int $line, ?int $col, string $expected)
     {
         $reporter = new GithubActionReporter($buffer = new BufferedOutput());
diff --git a/Tests/Command/CommandTest.php b/Tests/Command/CommandTest.php
index 0db3572fc..66b916738 100644
--- a/Tests/Command/CommandTest.php
+++ b/Tests/Command/CommandTest.php
@@ -11,14 +11,14 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\TestWithJson;
 use PHPUnit\Framework\TestCase;
-use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Attribute\AsCommand;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Exception\InvalidOptionException;
 use Symfony\Component\Console\Helper\FormatterHelper;
-use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputDefinition;
 use Symfony\Component\Console\Input\InputInterface;
@@ -27,11 +27,10 @@
 use Symfony\Component\Console\Output\NullOutput;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\Console\Tests\Fixtures\InvokableTestCommand;
 
 class CommandTest extends TestCase
 {
-    use ExpectUserDeprecationMessageTrait;
-
     protected static string $fixturesPath;
 
     public static function setUpBeforeClass(): void
@@ -50,7 +49,7 @@ public function testCommandNameCannotBeEmpty()
     {
         $this->expectException(\LogicException::class);
         $this->expectExceptionMessage('The command defined in "Symfony\Component\Console\Command\Command" cannot have an empty name.');
-        (new Application())->add(new Command());
+        (new Application())->addCommand(new Command());
     }
 
     public function testSetApplication()
@@ -137,9 +136,7 @@ public function testGetNamespaceGetNameSetName()
         $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name');
     }
 
-    /**
-     * @dataProvider provideInvalidCommandNames
-     */
+    #[DataProvider('provideInvalidCommandNames')]
     public function testInvalidCommandNames($name)
     {
         $this->expectException(\InvalidArgumentException::class);
@@ -190,7 +187,7 @@ public function testGetProcessedHelp()
         $command = new \TestCommand();
         $command->setHelp('The %command.name% command does... Example: %command.full_name%.');
         $application = new Application();
-        $application->add($command);
+        $application->addCommand($command);
         $application->setDefaultCommand('namespace:name', true);
         $this->assertStringContainsString('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly in single command applications');
         $this->assertStringNotContainsString('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name% in single command applications');
@@ -205,6 +202,17 @@ public function testGetSetAliases()
         $this->assertEquals(['name1'], $command->getAliases(), '->setAliases() sets the aliases');
     }
 
+    #[TestWithJson('["name|alias1|alias2", "name", ["alias1", "alias2"], false]')]
+    #[TestWithJson('["|alias1|alias2", "alias1", ["alias2"], true]')]
+    public function testSetAliasesAndHiddenViaName(string $name, string $expectedName, array $expectedAliases, bool $expectedHidden)
+    {
+        $command = new Command($name);
+
+        self::assertSame($expectedName, $command->getName());
+        self::assertSame($expectedHidden, $command->isHidden());
+        self::assertSame($expectedAliases, $command->getAliases());
+    }
+
     public function testGetSynopsis()
     {
         $command = new \TestCommand();
@@ -291,6 +299,13 @@ public function testRunInteractive()
         $this->assertEquals('interact called'.\PHP_EOL.'execute called'.\PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive');
     }
 
+    public function testInvokableCommand()
+    {
+        $tester = new CommandTester(new InvokableTestCommand());
+
+        $this->assertSame(Command::SUCCESS, $tester->execute([]));
+    }
+
     public function testRunNonInteractive()
     {
         $tester = new CommandTester(new \TestCommand());
@@ -370,9 +385,7 @@ public static function getSetCodeBindToClosureTests()
         ];
     }
 
-    /**
-     * @dataProvider getSetCodeBindToClosureTests
-     */
+    #[DataProvider('getSetCodeBindToClosureTests')]
     public function testSetCodeBindToClosure($previouslyBound, $expected)
     {
         $code = createClosure();
@@ -444,42 +457,12 @@ public function testCommandAttribute()
         $this->assertSame('foo', $command->getName());
         $this->assertSame('desc', $command->getDescription());
         $this->assertSame('help', $command->getHelp());
+        $this->assertCount(2, $command->getUsages());
+        $this->assertStringContainsString('usage1', $command->getUsages()[0]);
         $this->assertTrue($command->isHidden());
         $this->assertSame(['f'], $command->getAliases());
     }
 
-    /**
-     * @group legacy
-     */
-    public function testCommandAttributeWithDeprecatedMethods()
-    {
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Method "Symfony\Component\Console\Command\Command::getDefaultName()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Method "Symfony\Component\Console\Command\Command::getDefaultDescription()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-
-        $this->assertSame('|foo|f', Php8Command::getDefaultName());
-        $this->assertSame('desc', Php8Command::getDefaultDescription());
-    }
-
-    public function testAttributeOverridesProperty()
-    {
-        $command = new MyAnnotatedCommand();
-
-        $this->assertSame('my:command', $command->getName());
-        $this->assertSame('This is a command I wrote all by myself', $command->getDescription());
-    }
-
-    /**
-     * @group legacy
-     */
-    public function testAttributeOverridesPropertyWithDeprecatedMethods()
-    {
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Method "Symfony\Component\Console\Command\Command::getDefaultName()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Method "Symfony\Component\Console\Command\Command::getDefaultDescription()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-
-        $this->assertSame('my:command', MyAnnotatedCommand::getDefaultName());
-        $this->assertSame('This is a command I wrote all by myself', MyAnnotatedCommand::getDefaultDescription());
-    }
-
     public function testDefaultCommand()
     {
         $apl = new Application();
@@ -493,29 +476,6 @@ public function testDefaultCommand()
 
         $this->assertEquals('foo2', $property->getValue($apl));
     }
-
-    /**
-     * @group legacy
-     */
-    public function testDeprecatedMethods()
-    {
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Overriding "Command::getDefaultName()" in "Symfony\Component\Console\Tests\Command\FooCommand" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Overriding "Command::getDefaultDescription()" in "Symfony\Component\Console\Tests\Command\FooCommand" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.');
-
-        new FooCommand();
-    }
-
-    /**
-     * @group legacy
-     */
-    public function testDeprecatedNonIntegerReturnTypeFromClosureCode()
-    {
-        $this->expectUserDeprecationMessage('Since symfony/console 7.3: Returning a non-integer value from the command "foo" is deprecated and will throw an exception in Symfony 8.0.');
-
-        $command = new Command('foo');
-        $command->setCode(function () {});
-        $command->run(new ArrayInput([]), new NullOutput());
-    }
 }
 
 // In order to get an unbound closure, we should create it outside a class
@@ -529,7 +489,7 @@ function createClosure()
     };
 }
 
-#[AsCommand(name: 'foo', description: 'desc', hidden: true, aliases: ['f'], help: 'help')]
+#[AsCommand(name: 'foo', description: 'desc', usages: ['usage1', 'usage2'], hidden: true, aliases: ['f'], help: 'help')]
 class Php8Command extends Command
 {
 }
@@ -538,24 +498,3 @@ class Php8Command extends Command
 class Php8Command2 extends Command
 {
 }
-
-#[AsCommand(name: 'my:command', description: 'This is a command I wrote all by myself')]
-class MyAnnotatedCommand extends Command
-{
-    protected static $defaultName = 'i-shall-be-ignored';
-
-    protected static $defaultDescription = 'This description should be ignored.';
-}
-
-class FooCommand extends Command
-{
-    public static function getDefaultName(): ?string
-    {
-        return 'foo';
-    }
-
-    public static function getDefaultDescription(): ?string
-    {
-        return 'foo description';
-    }
-}
diff --git a/Tests/Command/CompleteCommandTest.php b/Tests/Command/CompleteCommandTest.php
index 75519eb49..8f26b0360 100644
--- a/Tests/Command/CompleteCommandTest.php
+++ b/Tests/Command/CompleteCommandTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Command\Command;
@@ -33,7 +34,7 @@ protected function setUp(): void
         $this->command = new CompleteCommand();
 
         $this->application = new Application();
-        $this->application->add(new CompleteCommandTest_HelloCommand());
+        $this->application->addCommand(new CompleteCommandTest_HelloCommand());
 
         $this->command->setApplication($this->application);
         $this->tester = new CommandTester($this->command);
@@ -65,9 +66,7 @@ public function testAdditionalShellSupport()
         $this->execute(['--shell' => 'bash', '--current' => '1', '--input' => ['bin/console']]);
     }
 
-    /**
-     * @dataProvider provideInputAndCurrentOptionValues
-     */
+    #[DataProvider('provideInputAndCurrentOptionValues')]
     public function testInputAndCurrentOptionValidation(array $input, ?string $exceptionMessage)
     {
         if ($exceptionMessage) {
@@ -91,9 +90,7 @@ public static function provideInputAndCurrentOptionValues()
         yield [['--current' => '2', '--input' => ['bin/console', 'cache:clear']], null];
     }
 
-    /**
-     * @dataProvider provideCompleteCommandNameInputs
-     */
+    #[DataProvider('provideCompleteCommandNameInputs')]
     public function testCompleteCommandName(array $input, array $suggestions)
     {
         $this->execute(['--current' => '1', '--input' => $input]);
@@ -108,9 +105,7 @@ public static function provideCompleteCommandNameInputs()
         yield 'complete-aliases' => [['bin/console', 'ah'], ['hello', 'ahoy']];
     }
 
-    /**
-     * @dataProvider provideCompleteCommandInputDefinitionInputs
-     */
+    #[DataProvider('provideCompleteCommandInputDefinitionInputs')]
     public function testCompleteCommandInputDefinition(array $input, array $suggestions)
     {
         $this->execute(['--current' => '2', '--input' => $input]);
diff --git a/Tests/Command/DumpCompletionCommandTest.php b/Tests/Command/DumpCompletionCommandTest.php
index ba23bb331..a69711e0e 100644
--- a/Tests/Command/DumpCompletionCommandTest.php
+++ b/Tests/Command/DumpCompletionCommandTest.php
@@ -11,15 +11,14 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Command\DumpCompletionCommand;
 use Symfony\Component\Console\Tester\CommandCompletionTester;
 
 class DumpCompletionCommandTest extends TestCase
 {
-    /**
-     * @dataProvider provideCompletionSuggestions
-     */
+    #[DataProvider('provideCompletionSuggestions')]
     public function testComplete(array $input, array $expectedSuggestions)
     {
         $tester = new CommandCompletionTester(new DumpCompletionCommand());
diff --git a/Tests/Command/HelpCommandTest.php b/Tests/Command/HelpCommandTest.php
index c36ab62df..160d78560 100644
--- a/Tests/Command/HelpCommandTest.php
+++ b/Tests/Command/HelpCommandTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Command\HelpCommand;
@@ -70,14 +71,12 @@ public function testExecuteForApplicationCommandWithXmlOption()
         $this->assertStringContainsString('<command', $commandTester->getDisplay(), '->execute() returns an XML help text if --format=xml is passed');
     }
 
-    /**
-     * @dataProvider provideCompletionSuggestions
-     */
+    #[DataProvider('provideCompletionSuggestions')]
     public function testComplete(array $input, array $expectedSuggestions)
     {
         require_once realpath(__DIR__.'/../Fixtures/FooCommand.php');
         $application = new Application();
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $tester = new CommandCompletionTester($application->get('help'));
         $suggestions = $tester->complete($input, 2);
         $this->assertSame($expectedSuggestions, $suggestions);
diff --git a/Tests/Command/InvokableCommandTest.php b/Tests/Command/InvokableCommandTest.php
index 9fc40809a..1eac29944 100644
--- a/Tests/Command/InvokableCommandTest.php
+++ b/Tests/Command/InvokableCommandTest.php
@@ -11,6 +11,8 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Assert;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Attribute\Argument;
 use Symfony\Component\Console\Attribute\Option;
@@ -18,6 +20,7 @@
 use Symfony\Component\Console\Completion\CompletionInput;
 use Symfony\Component\Console\Completion\CompletionSuggestions;
 use Symfony\Component\Console\Completion\Suggestion;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
 use Symfony\Component\Console\Exception\InvalidOptionException;
 use Symfony\Component\Console\Exception\LogicException;
 use Symfony\Component\Console\Input\ArrayInput;
@@ -132,6 +135,88 @@ public function testCommandInputOptionDefinition()
         self::assertFalse($optInputOption->getDefault());
     }
 
+    public function testEnumArgument()
+    {
+        $command = new Command('foo');
+        $command->setCode(function (
+            #[Argument] StringEnum $enum,
+            #[Argument] StringEnum $enumWithDefault = StringEnum::Image,
+            #[Argument] ?StringEnum $nullableEnum = null,
+        ): int {
+            Assert::assertSame(StringEnum::Image, $enum);
+            Assert::assertSame(StringEnum::Image, $enumWithDefault);
+            Assert::assertNull($nullableEnum);
+
+            return 0;
+        });
+
+        $enumInputArgument = $command->getDefinition()->getArgument('enum');
+        self::assertTrue($enumInputArgument->isRequired());
+        self::assertNull($enumInputArgument->getDefault());
+        self::assertTrue($enumInputArgument->hasCompletion());
+
+        $enumWithDefaultInputArgument = $command->getDefinition()->getArgument('enum-with-default');
+        self::assertFalse($enumWithDefaultInputArgument->isRequired());
+        self::assertSame('image', $enumWithDefaultInputArgument->getDefault());
+        self::assertTrue($enumWithDefaultInputArgument->hasCompletion());
+
+        $nullableEnumInputArgument = $command->getDefinition()->getArgument('nullable-enum');
+        self::assertFalse($nullableEnumInputArgument->isRequired());
+        self::assertNull($nullableEnumInputArgument->getDefault());
+        self::assertTrue($nullableEnumInputArgument->hasCompletion());
+
+        $enumInputArgument->complete(CompletionInput::fromTokens([], 0), $suggestions = new CompletionSuggestions());
+        self::assertEquals([new Suggestion('image'), new Suggestion('video')], $suggestions->getValueSuggestions());
+
+        $command->run(new ArrayInput(['enum' => 'image']), new NullOutput());
+
+        self::expectException(InvalidArgumentException::class);
+        self::expectExceptionMessage('The value "incorrect" is not valid for the "enum" argument. Supported values are "image", "video".');
+
+        $command->run(new ArrayInput(['enum' => 'incorrect']), new NullOutput());
+    }
+
+    public function testEnumOption()
+    {
+        $command = new Command('foo');
+        $command->setCode(function (
+            #[Option] StringEnum $enum = StringEnum::Video,
+            #[Option] StringEnum $enumWithDefault = StringEnum::Image,
+            #[Option] ?StringEnum $nullableEnum = null,
+        ): int {
+            Assert::assertSame(StringEnum::Image, $enum);
+            Assert::assertSame(StringEnum::Image, $enumWithDefault);
+            Assert::assertNull($nullableEnum);
+
+            return 0;
+        });
+
+        $enumInputOption = $command->getDefinition()->getOption('enum');
+        self::assertTrue($enumInputOption->isValueRequired());
+        self::assertSame('video', $enumInputOption->getDefault());
+        self::assertTrue($enumInputOption->hasCompletion());
+
+        $enumWithDefaultInputOption = $command->getDefinition()->getOption('enum-with-default');
+        self::assertTrue($enumWithDefaultInputOption->isValueRequired());
+        self::assertSame('image', $enumWithDefaultInputOption->getDefault());
+        self::assertTrue($enumWithDefaultInputOption->hasCompletion());
+
+        $nullableEnumInputOption = $command->getDefinition()->getOption('nullable-enum');
+        self::assertTrue($nullableEnumInputOption->isValueRequired());
+        self::assertNull($nullableEnumInputOption->getDefault());
+        self::assertTrue($nullableEnumInputOption->hasCompletion());
+
+        $enumInputOption->complete(CompletionInput::fromTokens([], 0), $suggestions = new CompletionSuggestions());
+        self::assertEquals([new Suggestion('image'), new Suggestion('video')], $suggestions->getValueSuggestions());
+
+        $command->run(new ArrayInput(['--enum' => 'image']), new NullOutput());
+
+        self::expectException(InvalidOptionException::class);
+        self::expectExceptionMessage('The value "incorrect" is not valid for the "enum" option. Supported values are "image", "video".');
+
+        $command->run(new ArrayInput(['--enum' => 'incorrect']), new NullOutput());
+    }
+
     public function testInvalidArgumentType()
     {
         $command = new Command('foo');
@@ -208,9 +293,7 @@ public function __invoke()
         $command->run(new ArrayInput([]), new NullOutput());
     }
 
-    /**
-     * @dataProvider provideInputArguments
-     */
+    #[DataProvider('provideInputArguments')]
     public function testInputArguments(array $parameters, array $expected)
     {
         $command = new Command('foo');
@@ -238,9 +321,7 @@ public static function provideInputArguments(): \Generator
         yield 'required & without-value' => [['a' => 'x', 'b' => null, 'c' => null, 'd' => null], ['x', null, '', []]];
     }
 
-    /**
-     * @dataProvider provideBinaryInputOptions
-     */
+    #[DataProvider('provideBinaryInputOptions')]
     public function testBinaryInputOptions(array $parameters, array $expected)
     {
         $command = new Command('foo');
@@ -266,9 +347,7 @@ public static function provideBinaryInputOptions(): \Generator
         yield 'negative' => [['--no-a' => null, '--no-c' => null], [false, false, false]];
     }
 
-    /**
-     * @dataProvider provideNonBinaryInputOptions
-     */
+    #[DataProvider('provideNonBinaryInputOptions')]
     public function testNonBinaryInputOptions(array $parameters, array $expected)
     {
         $command = new Command('foo');
@@ -321,9 +400,7 @@ public static function provideNonBinaryInputOptions(): \Generator
         ];
     }
 
-    /**
-     * @dataProvider provideInvalidOptionDefinitions
-     */
+    #[DataProvider('provideInvalidOptionDefinitions')]
     public function testInvalidOptionDefinition(callable $code)
     {
         $command = new Command('foo');
@@ -381,3 +458,9 @@ public function getSuggestedRoles(CompletionInput $input): array
         return ['ROLE_ADMIN', 'ROLE_USER'];
     }
 }
+
+enum StringEnum: string
+{
+    case Image = 'image';
+    case Video = 'video';
+}
diff --git a/Tests/Command/ListCommandTest.php b/Tests/Command/ListCommandTest.php
index a6ffc8ab5..7efdb5ab5 100644
--- a/Tests/Command/ListCommandTest.php
+++ b/Tests/Command/ListCommandTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Command;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Tester\CommandCompletionTester;
@@ -54,7 +55,7 @@ public function testExecuteListsCommandsWithNamespaceArgument()
     {
         require_once realpath(__DIR__.'/../Fixtures/FooCommand.php');
         $application = new Application();
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $commandTester = new CommandTester($command = $application->get('list'));
         $commandTester->execute(['command' => $command->getName(), 'namespace' => 'foo', '--raw' => true]);
         $output = <<<'EOF'
@@ -69,7 +70,7 @@ public function testExecuteListsCommandsOrder()
     {
         require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php');
         $application = new Application();
-        $application->add(new \Foo6Command());
+        $application->addCommand(new \Foo6Command());
         $commandTester = new CommandTester($command = $application->get('list'));
         $commandTester->execute(['command' => $command->getName()], ['decorated' => false]);
         $output = <<<'EOF'
@@ -102,7 +103,7 @@ public function testExecuteListsCommandsOrderRaw()
     {
         require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php');
         $application = new Application();
-        $application->add(new \Foo6Command());
+        $application->addCommand(new \Foo6Command());
         $commandTester = new CommandTester($command = $application->get('list'));
         $commandTester->execute(['command' => $command->getName(), '--raw' => true]);
         $output = <<<'EOF'
@@ -115,14 +116,12 @@ public function testExecuteListsCommandsOrderRaw()
         $this->assertEquals($output, trim($commandTester->getDisplay(true)));
     }
 
-    /**
-     * @dataProvider provideCompletionSuggestions
-     */
+    #[DataProvider('provideCompletionSuggestions')]
     public function testComplete(array $input, array $expectedSuggestions)
     {
         require_once realpath(__DIR__.'/../Fixtures/FooCommand.php');
         $application = new Application();
-        $application->add(new \FooCommand());
+        $application->addCommand(new \FooCommand());
         $tester = new CommandCompletionTester($application->get('list'));
         $suggestions = $tester->complete($input, 2);
         $this->assertSame($expectedSuggestions, $suggestions);
diff --git a/Tests/Command/TraceableCommandTest.php b/Tests/Command/TraceableCommandTest.php
index 1bf709f8b..eab84c549 100644
--- a/Tests/Command/TraceableCommandTest.php
+++ b/Tests/Command/TraceableCommandTest.php
@@ -25,7 +25,7 @@ class TraceableCommandTest extends TestCase
     protected function setUp(): void
     {
         $this->application = new Application();
-        $this->application->add(new LoopExampleCommand());
+        $this->application->addCommand(new LoopExampleCommand());
     }
 
     public function testRunIsOverriddenWithoutProfile()
@@ -47,7 +47,7 @@ public function testRunIsNotOverriddenWithProfile()
         $command = new LoopExampleCommand();
         $traceableCommand = new TraceableCommand($command, new Stopwatch());
 
-        $this->application->add($traceableCommand);
+        $this->application->addCommand($traceableCommand);
 
         $commandTester = new CommandTester($traceableCommand);
         $commandTester->execute([]);
diff --git a/Tests/Completion/CompletionInputTest.php b/Tests/Completion/CompletionInputTest.php
index df0d081fd..58493fdf0 100644
--- a/Tests/Completion/CompletionInputTest.php
+++ b/Tests/Completion/CompletionInputTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Completion;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Completion\CompletionInput;
 use Symfony\Component\Console\Input\InputArgument;
@@ -19,9 +20,7 @@
 
 class CompletionInputTest extends TestCase
 {
-    /**
-     * @dataProvider provideBindData
-     */
+    #[DataProvider('provideBindData')]
     public function testBind(CompletionInput $input, string $expectedType, ?string $expectedName, string $expectedValue)
     {
         $definition = new InputDefinition([
@@ -74,9 +73,7 @@ public static function provideBindData()
         yield 'end' => [CompletionInput::fromTokens(['bin/console', 'symfony', 'sensiolabs'], 3), CompletionInput::TYPE_NONE, null, ''];
     }
 
-    /**
-     * @dataProvider provideBindWithLastArrayArgumentData
-     */
+    #[DataProvider('provideBindWithLastArrayArgumentData')]
     public function testBindWithLastArrayArgument(CompletionInput $input, ?string $expectedValue)
     {
         $definition = new InputDefinition([
@@ -111,9 +108,7 @@ public function testBindArgumentWithDefault()
         $this->assertEquals('', $input->getCompletionValue(), 'Unexpected value');
     }
 
-    /**
-     * @dataProvider provideFromStringData
-     */
+    #[DataProvider('provideFromStringData')]
     public function testFromString($inputStr, array $expectedTokens)
     {
         $input = CompletionInput::fromString($inputStr, 1);
diff --git a/Tests/ConsoleEventsTest.php b/Tests/ConsoleEventsTest.php
index 408f8c0d3..3421eda80 100644
--- a/Tests/ConsoleEventsTest.php
+++ b/Tests/ConsoleEventsTest.php
@@ -58,7 +58,7 @@ public function testEventAliases()
             ->setPublic(true)
             ->addMethodCall('setAutoExit', [false])
             ->addMethodCall('setDispatcher', [new Reference('event_dispatcher')])
-            ->addMethodCall('add', [new Reference('failing_command')])
+            ->addMethodCall('addCommand', [new Reference('failing_command')])
         ;
 
         $container->compile();
diff --git a/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/Tests/DependencyInjection/AddConsoleCommandPassTest.php
index 9ac660100..953e5843c 100644
--- a/Tests/DependencyInjection/AddConsoleCommandPassTest.php
+++ b/Tests/DependencyInjection/AddConsoleCommandPassTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\DependencyInjection;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Attribute\AsCommand;
 use Symfony\Component\Console\Command\Command;
@@ -28,9 +29,7 @@
 
 class AddConsoleCommandPassTest extends TestCase
 {
-    /**
-     * @dataProvider visibilityProvider
-     */
+    #[DataProvider('visibilityProvider')]
     public function testProcess($public)
     {
         $container = new ContainerBuilder();
@@ -315,6 +314,7 @@ public function testProcessInvokableCommand()
         $definition->addTag('console.command', [
             'command' => 'invokable',
             'description' => 'The command description',
+            'usages' => ['usage1', 'usage2'],
             'help' => 'The %command.name% command help content.',
         ]);
         $container->setDefinition('invokable_command', $definition);
@@ -325,6 +325,8 @@ public function testProcessInvokableCommand()
         self::assertTrue($container->has('invokable_command.command'));
         self::assertSame('The command description', $command->getDescription());
         self::assertSame('The %command.name% command help content.', $command->getHelp());
+        self::assertCount(2, $command->getUsages());
+        $this->assertStringContainsString('usage1', $command->getUsages()[0]);
     }
 
     public function testProcessInvokableSignalableCommand()
diff --git a/Tests/Descriptor/AbstractDescriptorTestCase.php b/Tests/Descriptor/AbstractDescriptorTestCase.php
index 93658f4be..0c4eee158 100644
--- a/Tests/Descriptor/AbstractDescriptorTestCase.php
+++ b/Tests/Descriptor/AbstractDescriptorTestCase.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Descriptor;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Command\Command;
@@ -21,31 +22,31 @@
 
 abstract class AbstractDescriptorTestCase extends TestCase
 {
-    /** @dataProvider getDescribeInputArgumentTestData */
+    #[DataProvider('getDescribeInputArgumentTestData')]
     public function testDescribeInputArgument(InputArgument $argument, $expectedDescription)
     {
         $this->assertDescription($expectedDescription, $argument);
     }
 
-    /** @dataProvider getDescribeInputOptionTestData */
+    #[DataProvider('getDescribeInputOptionTestData')]
     public function testDescribeInputOption(InputOption $option, $expectedDescription)
     {
         $this->assertDescription($expectedDescription, $option);
     }
 
-    /** @dataProvider getDescribeInputDefinitionTestData */
+    #[DataProvider('getDescribeInputDefinitionTestData')]
     public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription)
     {
         $this->assertDescription($expectedDescription, $definition);
     }
 
-    /** @dataProvider getDescribeCommandTestData */
+    #[DataProvider('getDescribeCommandTestData')]
     public function testDescribeCommand(Command $command, $expectedDescription)
     {
         $this->assertDescription($expectedDescription, $command);
     }
 
-    /** @dataProvider getDescribeApplicationTestData */
+    #[DataProvider('getDescribeApplicationTestData')]
     public function testDescribeApplication(Application $application, $expectedDescription)
     {
         // the "completion" command has dynamic help information depending on the shell
diff --git a/Tests/Descriptor/ApplicationDescriptionTest.php b/Tests/Descriptor/ApplicationDescriptionTest.php
index 1933c985c..a6117952a 100644
--- a/Tests/Descriptor/ApplicationDescriptionTest.php
+++ b/Tests/Descriptor/ApplicationDescriptionTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Descriptor;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Command\Command;
@@ -18,14 +19,12 @@
 
 final class ApplicationDescriptionTest extends TestCase
 {
-    /**
-     * @dataProvider getNamespacesProvider
-     */
+    #[DataProvider('getNamespacesProvider')]
     public function testGetNamespaces(array $expected, array $names)
     {
         $application = new TestApplication();
         foreach ($names as $name) {
-            $application->add(new Command($name));
+            $application->addCommand(new Command($name));
         }
 
         $this->assertSame($expected, array_keys((new ApplicationDescription($application))->getNamespaces()));
diff --git a/Tests/Fixtures/DescriptorApplication2.php b/Tests/Fixtures/DescriptorApplication2.php
index 7bb02fa54..c755bab38 100644
--- a/Tests/Fixtures/DescriptorApplication2.php
+++ b/Tests/Fixtures/DescriptorApplication2.php
@@ -18,9 +18,9 @@ class DescriptorApplication2 extends Application
     public function __construct()
     {
         parent::__construct('My Symfony application', 'v1.0');
-        $this->add(new DescriptorCommand1());
-        $this->add(new DescriptorCommand2());
-        $this->add(new DescriptorCommand3());
-        $this->add(new DescriptorCommand4());
+        $this->addCommand(new DescriptorCommand1());
+        $this->addCommand(new DescriptorCommand2());
+        $this->addCommand(new DescriptorCommand3());
+        $this->addCommand(new DescriptorCommand4());
     }
 }
diff --git a/Tests/Fixtures/DescriptorApplicationMbString.php b/Tests/Fixtures/DescriptorApplicationMbString.php
index bf170c449..a76e0e181 100644
--- a/Tests/Fixtures/DescriptorApplicationMbString.php
+++ b/Tests/Fixtures/DescriptorApplicationMbString.php
@@ -19,6 +19,6 @@ public function __construct()
     {
         parent::__construct('MbString åpplicätion');
 
-        $this->add(new DescriptorCommandMbString());
+        $this->addCommand(new DescriptorCommandMbString());
     }
 }
diff --git a/Tests/Fixtures/InvokableExtendingCommandTestCommand.php b/Tests/Fixtures/InvokableExtendingCommandTestCommand.php
new file mode 100644
index 000000000..724951608
--- /dev/null
+++ b/Tests/Fixtures/InvokableExtendingCommandTestCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Symfony\Component\Console\Tests\Fixtures;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+
+#[AsCommand('invokable:test')]
+class InvokableExtendingCommandTestCommand extends Command
+{
+    public function __invoke(): int
+    {
+        return Command::SUCCESS;
+    }
+}
diff --git a/Tests/Fixtures/InvokableTestCommand.php b/Tests/Fixtures/InvokableTestCommand.php
new file mode 100644
index 000000000..1fbe32e3c
--- /dev/null
+++ b/Tests/Fixtures/InvokableTestCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Symfony\Component\Console\Tests\Fixtures;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+
+#[AsCommand('invokable:test')]
+class InvokableTestCommand
+{
+    public function __invoke(): int
+    {
+        return Command::SUCCESS;
+    }
+}
diff --git a/Tests/Formatter/OutputFormatterTest.php b/Tests/Formatter/OutputFormatterTest.php
index 489108bd5..70c6cc3fb 100644
--- a/Tests/Formatter/OutputFormatterTest.php
+++ b/Tests/Formatter/OutputFormatterTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Formatter;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Formatter\OutputFormatter;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@@ -159,9 +160,7 @@ public function testInlineStyle()
         $this->assertEquals("\033[34;41msome text\033[39;49m", $formatter->format('<fg=blue;bg=red>some text</fg=blue;bg=red>'));
     }
 
-    /**
-     * @dataProvider provideInlineStyleOptionsCases
-     */
+    #[DataProvider('provideInlineStyleOptionsCases')]
     public function testInlineStyleOptions(string $tag, ?string $expected = null, ?string $input = null, bool $truecolor = false)
     {
         if ($truecolor && 'truecolor' !== getenv('COLORTERM')) {
@@ -177,7 +176,7 @@ public function testInlineStyleOptions(string $tag, ?string $expected = null, ?s
             $expected = $tag.$input.'</'.$styleString.'>';
             $this->assertSame($expected, $formatter->format($expected));
         } else {
-            /** @var OutputFormatterStyle $result */
+            /* @var OutputFormatterStyle $result */
             $this->assertInstanceOf(OutputFormatterStyle::class, $result);
             $this->assertSame($expected, $formatter->format($tag.$input.'</>'));
             $this->assertSame($expected, $formatter->format($tag.$input.'</'.$styleString.'>'));
@@ -241,9 +240,7 @@ public function testFormatterHasStyles()
         $this->assertTrue($formatter->hasStyle('question'));
     }
 
-    /**
-     * @dataProvider provideDecoratedAndNonDecoratedOutput
-     */
+    #[DataProvider('provideDecoratedAndNonDecoratedOutput')]
     public function testNotDecoratedFormatterOnJediTermEmulator(string $input, string $expectedNonDecoratedOutput, string $expectedDecoratedOutput, bool $shouldBeJediTerm = false)
     {
         $terminalEmulator = $shouldBeJediTerm ? 'JetBrains-JediTerm' : 'Unknown';
@@ -259,9 +256,7 @@ public function testNotDecoratedFormatterOnJediTermEmulator(string $input, strin
         }
     }
 
-    /**
-     * @dataProvider provideDecoratedAndNonDecoratedOutput
-     */
+    #[DataProvider('provideDecoratedAndNonDecoratedOutput')]
     public function testNotDecoratedFormatterOnIDEALikeEnvironment(string $input, string $expectedNonDecoratedOutput, string $expectedDecoratedOutput, bool $expectsIDEALikeTerminal = false)
     {
         // Backup previous env variable
diff --git a/Tests/Helper/DumperNativeFallbackTest.php b/Tests/Helper/DumperNativeFallbackTest.php
index 1b37e4e93..892590dd7 100644
--- a/Tests/Helper/DumperNativeFallbackTest.php
+++ b/Tests/Helper/DumperNativeFallbackTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Bridge\PhpUnit\ClassExistsMock;
 use Symfony\Component\Console\Helper\Dumper;
@@ -32,9 +33,7 @@ public static function tearDownAfterClass(): void
         ClassExistsMock::withMockedClasses([]);
     }
 
-    /**
-     * @dataProvider provideVariables
-     */
+    #[DataProvider('provideVariables')]
     public function testInvoke($variable, $primitiveString)
     {
         $dumper = new Dumper($this->createMock(OutputInterface::class));
diff --git a/Tests/Helper/DumperTest.php b/Tests/Helper/DumperTest.php
index 0a30c6ec3..344d3f4dc 100644
--- a/Tests/Helper/DumperTest.php
+++ b/Tests/Helper/DumperTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Helper\Dumper;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -32,9 +33,7 @@ public static function tearDownAfterClass(): void
         putenv('DUMP_COMMA_SEPARATOR');
     }
 
-    /**
-     * @dataProvider provideVariables
-     */
+    #[DataProvider('provideVariables')]
     public function testInvoke($variable)
     {
         $output = $this->createMock(OutputInterface::class);
diff --git a/Tests/Helper/HelperTest.php b/Tests/Helper/HelperTest.php
index 009864454..95d5ee5d3 100644
--- a/Tests/Helper/HelperTest.php
+++ b/Tests/Helper/HelperTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Formatter\OutputFormatter;
 use Symfony\Component\Console\Helper\Helper;
@@ -61,17 +62,13 @@ public static function decoratedTextProvider()
         ];
     }
 
-    /**
-     * @dataProvider formatTimeProvider
-     */
+    #[DataProvider('formatTimeProvider')]
     public function testFormatTime(int|float $secs, string $expectedFormat, int $precision)
     {
         $this->assertEquals($expectedFormat, Helper::formatTime($secs, $precision));
     }
 
-    /**
-     * @dataProvider decoratedTextProvider
-     */
+    #[DataProvider('decoratedTextProvider')]
     public function testRemoveDecoration(string $decoratedText, string $undecoratedText)
     {
         $this->assertEquals($undecoratedText, Helper::removeDecoration(new OutputFormatter(), $decoratedText));
diff --git a/Tests/Helper/OutputWrapperTest.php b/Tests/Helper/OutputWrapperTest.php
index 2ce15b6d4..d09fd9ea9 100644
--- a/Tests/Helper/OutputWrapperTest.php
+++ b/Tests/Helper/OutputWrapperTest.php
@@ -11,14 +11,13 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Helper\OutputWrapper;
 
 class OutputWrapperTest extends TestCase
 {
-    /**
-     * @dataProvider textProvider
-     */
+    #[DataProvider('textProvider')]
     public function testBasicWrap(string $text, int $width, bool $allowCutUrls, string $expected)
     {
         $wrapper = new OutputWrapper($allowCutUrls);
diff --git a/Tests/Helper/ProcessHelperTest.php b/Tests/Helper/ProcessHelperTest.php
index 1fd88987b..02d9fb939 100644
--- a/Tests/Helper/ProcessHelperTest.php
+++ b/Tests/Helper/ProcessHelperTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Helper\DebugFormatterHelper;
 use Symfony\Component\Console\Helper\HelperSet;
@@ -20,9 +21,7 @@
 
 class ProcessHelperTest extends TestCase
 {
-    /**
-     * @dataProvider provideCommandsAndOutput
-     */
+    #[DataProvider('provideCommandsAndOutput')]
     public function testVariousProcessRuns(string $expected, Process|string|array $cmd, int $verbosity, ?string $error)
     {
         if (\is_string($cmd)) {
diff --git a/Tests/Helper/ProgressBarTest.php b/Tests/Helper/ProgressBarTest.php
index c0278cc33..683bc7c87 100644
--- a/Tests/Helper/ProgressBarTest.php
+++ b/Tests/Helper/ProgressBarTest.php
@@ -11,6 +11,8 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Formatter\OutputFormatter;
 use Symfony\Component\Console\Helper\Helper;
@@ -18,9 +20,7 @@
 use Symfony\Component\Console\Output\ConsoleSectionOutput;
 use Symfony\Component\Console\Output\StreamOutput;
 
-/**
- * @group time-sensitive
- */
+#[Group('time-sensitive')]
 class ProgressBarTest extends TestCase
 {
     private string|false $colSize;
@@ -1117,9 +1117,7 @@ public function testUnicode()
         $bar->finish();
     }
 
-    /**
-     * @dataProvider provideFormat
-     */
+    #[DataProvider('provideFormat')]
     public function testFormatsWithoutMax($format)
     {
         $bar = new ProgressBar($output = $this->getOutputStream(), 0, 0);
diff --git a/Tests/Helper/ProgressIndicatorTest.php b/Tests/Helper/ProgressIndicatorTest.php
index 2a4441d57..fb11d1432 100644
--- a/Tests/Helper/ProgressIndicatorTest.php
+++ b/Tests/Helper/ProgressIndicatorTest.php
@@ -11,13 +11,13 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Helper\ProgressIndicator;
 use Symfony\Component\Console\Output\StreamOutput;
 
-/**
- * @group time-sensitive
- */
+#[Group('time-sensitive')]
 class ProgressIndicatorTest extends TestCase
 {
     public function testDefaultIndicator()
@@ -176,9 +176,7 @@ public function testCannotFinishUnstartedIndicator()
         $bar->finish('Finished');
     }
 
-    /**
-     * @dataProvider provideFormat
-     */
+    #[DataProvider('provideFormat')]
     public function testFormats($format)
     {
         $bar = new ProgressIndicator($output = $this->getOutputStream(), $format);
diff --git a/Tests/Helper/QuestionHelperTest.php b/Tests/Helper/QuestionHelperTest.php
index 0e91dd85b..e3c364acf 100644
--- a/Tests/Helper/QuestionHelperTest.php
+++ b/Tests/Helper/QuestionHelperTest.php
@@ -11,6 +11,8 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
 use Symfony\Component\Console\Application;
 use Symfony\Component\Console\Exception\InvalidArgumentException;
 use Symfony\Component\Console\Exception\MissingInputException;
@@ -27,9 +29,7 @@
 use Symfony\Component\Console\Terminal;
 use Symfony\Component\Console\Tester\ApplicationTester;
 
-/**
- * @group tty
- */
+#[Group('tty')]
 class QuestionHelperTest extends AbstractQuestionHelperTestCase
 {
     public function testAskChoice()
@@ -348,9 +348,7 @@ public static function getInputs()
         ];
     }
 
-    /**
-     * @dataProvider getInputs
-     */
+    #[DataProvider('getInputs')]
     public function testAskWithAutocompleteWithMultiByteCharacter($character)
     {
         if (!Terminal::hasSttyAvailable()) {
@@ -522,9 +520,7 @@ public function testAskMultilineResponseWithWithCursorInMiddleOfSeekableInputStr
         $this->assertSame(8, ftell($response));
     }
 
-    /**
-     * @dataProvider getAskConfirmationData
-     */
+    #[DataProvider('getAskConfirmationData')]
     public function testAskConfirmation($question, $expected, $default = true)
     {
         $dialog = new QuestionHelper();
@@ -565,7 +561,7 @@ public function testAskAndValidate()
 
         $error = 'This is not a color!';
         $validator = function ($color) use ($error) {
-            if (!\in_array($color, ['white', 'black'])) {
+            if (!\in_array($color, ['white', 'black'], true)) {
                 throw new \InvalidArgumentException($error);
             }
 
@@ -588,9 +584,7 @@ public function testAskAndValidate()
         }
     }
 
-    /**
-     * @dataProvider simpleAnswerProvider
-     */
+    #[DataProvider('simpleAnswerProvider')]
     public function testSelectChoiceFromSimpleChoices($providedAnswer, $expectedValue)
     {
         $possibleChoices = [
@@ -622,9 +616,7 @@ public static function simpleAnswerProvider()
         ];
     }
 
-    /**
-     * @dataProvider specialCharacterInMultipleChoice
-     */
+    #[DataProvider('specialCharacterInMultipleChoice')]
     public function testSpecialCharacterChoiceFromMultipleChoiceList($providedAnswer, $expectedValue)
     {
         $possibleChoices = [
@@ -653,9 +645,7 @@ public static function specialCharacterInMultipleChoice()
         ];
     }
 
-    /**
-     * @dataProvider answerProvider
-     */
+    #[DataProvider('answerProvider')]
     public function testSelectChoiceFromChoiceList($providedAnswer, $expectedValue)
     {
         $possibleChoices = [
@@ -750,7 +740,7 @@ public function testAskThrowsExceptionOnMissingInput()
     public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
     {
         $this->expectException(MissingInputException::class);
-        $this->expectExceptionMessage('Aborted.');
+        $this->expectExceptionMessage('Aborted while asking: Choice');
         (new QuestionHelper())->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
     }
 
diff --git a/Tests/Helper/SymfonyQuestionHelperTest.php b/Tests/Helper/SymfonyQuestionHelperTest.php
index 6cf79965b..56b8f210d 100644
--- a/Tests/Helper/SymfonyQuestionHelperTest.php
+++ b/Tests/Helper/SymfonyQuestionHelperTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\Group;
 use Symfony\Component\Console\Exception\RuntimeException;
 use Symfony\Component\Console\Helper\FormatterHelper;
 use Symfony\Component\Console\Helper\HelperSet;
@@ -20,9 +21,7 @@
 use Symfony\Component\Console\Question\ChoiceQuestion;
 use Symfony\Component\Console\Question\Question;
 
-/**
- * @group tty
- */
+#[Group('tty')]
 class SymfonyQuestionHelperTest extends AbstractQuestionHelperTestCase
 {
     public function testAskChoice()
diff --git a/Tests/Helper/TableTest.php b/Tests/Helper/TableTest.php
index eb85364da..4ab606b54 100644
--- a/Tests/Helper/TableTest.php
+++ b/Tests/Helper/TableTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Helper;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Exception\InvalidArgumentException;
 use Symfony\Component\Console\Exception\RuntimeException;
@@ -38,9 +39,7 @@ protected function tearDown(): void
         unset($this->stream);
     }
 
-    /**
-     * @dataProvider renderProvider
-     */
+    #[DataProvider('renderProvider')]
     public function testRender($headers, $rows, $style, $expected, $decorated = false)
     {
         $table = new Table($output = $this->getOutputStream($decorated));
@@ -54,9 +53,7 @@ public function testRender($headers, $rows, $style, $expected, $decorated = fals
         $this->assertEquals($expected, $this->getOutputContent($output));
     }
 
-    /**
-     * @dataProvider renderProvider
-     */
+    #[DataProvider('renderProvider')]
     public function testRenderAddRows($headers, $rows, $style, $expected, $decorated = false)
     {
         $table = new Table($output = $this->getOutputStream($decorated));
@@ -70,9 +67,7 @@ public function testRenderAddRows($headers, $rows, $style, $expected, $decorated
         $this->assertEquals($expected, $this->getOutputContent($output));
     }
 
-    /**
-     * @dataProvider renderProvider
-     */
+    #[DataProvider('renderProvider')]
     public function testRenderAddRowsOneByOne($headers, $rows, $style, $expected, $decorated = false)
     {
         $table = new Table($output = $this->getOutputStream($decorated));
@@ -1260,9 +1255,7 @@ public function testGetStyleDefinition()
         Table::getStyleDefinition('absent');
     }
 
-    /**
-     * @dataProvider renderSetTitle
-     */
+    #[DataProvider('renderSetTitle')]
     public function testSetTitle($headerTitle, $footerTitle, $style, $expected)
     {
         (new Table($output = $this->getOutputStream()))
@@ -1536,9 +1529,7 @@ public static function provideRenderHorizontalTests()
         yield [$headers, $rows, $expected];
     }
 
-    /**
-     * @dataProvider provideRenderHorizontalTests
-     */
+    #[DataProvider('provideRenderHorizontalTests')]
     public function testRenderHorizontal(array $headers, array $rows, string $expected)
     {
         $table = new Table($output = $this->getOutputStream());
@@ -1984,9 +1975,7 @@ public static function provideRenderVerticalTests(): \Traversable
         ];
     }
 
-    /**
-     * @dataProvider provideRenderVerticalTests
-     */
+    #[DataProvider('provideRenderVerticalTests')]
     public function testVerticalRender(string $expectedOutput, array $headers, array $rows, string $style = 'default', string $headerTitle = '', string $footerTitle = '')
     {
         $table = new Table($output = $this->getOutputStream());
diff --git a/Tests/Input/ArgvInputTest.php b/Tests/Input/ArgvInputTest.php
index 0e76f9ee6..dead40f46 100644
--- a/Tests/Input/ArgvInputTest.php
+++ b/Tests/Input/ArgvInputTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Input;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Input\ArgvInput;
 use Symfony\Component\Console\Input\InputArgument;
@@ -39,9 +40,7 @@ public function testParseArguments()
         $this->assertSame(['name' => 'foo'], $input->getArguments(), '->parse() is stateless');
     }
 
-    /**
-     * @dataProvider provideOptions
-     */
+    #[DataProvider('provideOptions')]
     public function testParseOptions($input, $options, $expectedOptions, $message)
     {
         $input = new ArgvInput($input);
@@ -50,9 +49,7 @@ public function testParseOptions($input, $options, $expectedOptions, $message)
         $this->assertSame($expectedOptions, $input->getOptions(), $message);
     }
 
-    /**
-     * @dataProvider provideNegatableOptions
-     */
+    #[DataProvider('provideNegatableOptions')]
     public function testParseOptionsNegatable($input, $options, $expectedOptions, $message)
     {
         $input = new ArgvInput($input);
@@ -234,9 +231,7 @@ public static function provideNegatableOptions()
         ];
     }
 
-    /**
-     * @dataProvider provideInvalidInput
-     */
+    #[DataProvider('provideInvalidInput')]
     public function testInvalidInput($argv, $definition, $expectedExceptionMessage)
     {
         $this->expectException(\RuntimeException::class);
@@ -245,9 +240,7 @@ public function testInvalidInput($argv, $definition, $expectedExceptionMessage)
         (new ArgvInput($argv))->bind($definition);
     }
 
-    /**
-     * @dataProvider provideInvalidNegatableInput
-     */
+    #[DataProvider('provideInvalidNegatableInput')]
     public function testInvalidInputNegatable($argv, $definition, $expectedExceptionMessage)
     {
         $this->expectException(\RuntimeException::class);
@@ -509,9 +502,7 @@ public function testToString()
         $this->assertSame('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input);
     }
 
-    /**
-     * @dataProvider provideGetParameterOptionValues
-     */
+    #[DataProvider('provideGetParameterOptionValues')]
     public function testGetParameterOptionEqualSign($argv, $key, $default, $onlyParams, $expected)
     {
         $input = new ArgvInput($argv);
@@ -574,9 +565,7 @@ public function testGetRawTokensFalse()
         $this->assertSame(['--foo', 'bar'], $input->getRawTokens());
     }
 
-    /**
-     * @dataProvider provideGetRawTokensTrueTests
-     */
+    #[DataProvider('provideGetRawTokensTrueTests')]
     public function testGetRawTokensTrue(array $argv, array $expected)
     {
         $input = new ArgvInput($argv);
diff --git a/Tests/Input/ArrayInputTest.php b/Tests/Input/ArrayInputTest.php
index 74d2c089f..96bd08b87 100644
--- a/Tests/Input/ArrayInputTest.php
+++ b/Tests/Input/ArrayInputTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Input;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputArgument;
@@ -64,9 +65,7 @@ public function testParseArguments()
         $this->assertSame(['name' => 'foo'], $input->getArguments(), '->parse() parses required arguments');
     }
 
-    /**
-     * @dataProvider provideOptions
-     */
+    #[DataProvider('provideOptions')]
     public function testParseOptions($input, $options, $expectedOptions, $message)
     {
         $input = new ArrayInput($input, new InputDefinition($options));
@@ -122,9 +121,7 @@ public static function provideOptions(): array
         ];
     }
 
-    /**
-     * @dataProvider provideInvalidInput
-     */
+    #[DataProvider('provideInvalidInput')]
     public function testParseInvalidInput($parameters, $definition, $expectedExceptionMessage)
     {
         $this->expectException(\InvalidArgumentException::class);
diff --git a/Tests/Input/InputDefinitionTest.php b/Tests/Input/InputDefinitionTest.php
index ab203e6e5..3925ec8e5 100644
--- a/Tests/Input/InputDefinitionTest.php
+++ b/Tests/Input/InputDefinitionTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Input;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputDefinition;
@@ -359,9 +360,7 @@ public function testGetOptionDefaults()
         $this->assertSame($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options');
     }
 
-    /**
-     * @dataProvider getGetSynopsisData
-     */
+    #[DataProvider('getGetSynopsisData')]
     public function testGetSynopsis(InputDefinition $definition, $expectedSynopsis, $message = null)
     {
         $this->assertSame($expectedSynopsis, $definition->getSynopsis(), $message ? '->getSynopsis() '.$message : '');
diff --git a/Tests/Input/InputOptionTest.php b/Tests/Input/InputOptionTest.php
index 47ab503f7..e5cb3318a 100644
--- a/Tests/Input/InputOptionTest.php
+++ b/Tests/Input/InputOptionTest.php
@@ -73,8 +73,6 @@ public function testShortcut()
         $this->assertSame('0|z', $option->getShortcut(), '-0 is an acceptable shortcut value when embedded in an array');
         $option = new InputOption('foo', '0|z');
         $this->assertSame('0|z', $option->getShortcut(), '-0 is an acceptable shortcut value when embedded in a string-list');
-        $option = new InputOption('foo', false);
-        $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null when given a false as value');
     }
 
     public function testModes()
diff --git a/Tests/Input/StringInputTest.php b/Tests/Input/StringInputTest.php
index 92425daab..3a97938a2 100644
--- a/Tests/Input/StringInputTest.php
+++ b/Tests/Input/StringInputTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Input;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Input\ArgvInput;
 use Symfony\Component\Console\Input\InputDefinition;
@@ -19,9 +20,7 @@
 
 class StringInputTest extends TestCase
 {
-    /**
-     * @dataProvider getTokenizeData
-     */
+    #[DataProvider('getTokenizeData')]
     public function testTokenize($input, $tokens, $message)
     {
         $input = new StringInput($input);
diff --git a/Tests/Logger/ConsoleLoggerTest.php b/Tests/Logger/ConsoleLoggerTest.php
index 0464c8c5f..976ff7a98 100644
--- a/Tests/Logger/ConsoleLoggerTest.php
+++ b/Tests/Logger/ConsoleLoggerTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Logger;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\InvalidArgumentException;
 use Psr\Log\LoggerInterface;
@@ -54,9 +55,7 @@ public function getLogs(): array
         return $this->output->getLogs();
     }
 
-    /**
-     * @dataProvider provideOutputMappingParams
-     */
+    #[DataProvider('provideOutputMappingParams')]
     public function testOutputMapping($logLevel, $outputVerbosity, $isOutput, $addVerbosityLevelMap = [])
     {
         $out = new BufferedOutput($outputVerbosity);
@@ -104,9 +103,7 @@ public function testImplements()
         $this->assertInstanceOf(LoggerInterface::class, $this->getLogger());
     }
 
-    /**
-     * @dataProvider provideLevelsAndMessages
-     */
+    #[DataProvider('provideLevelsAndMessages')]
     public function testLogsAtAllLevels($level, $message)
     {
         $logger = $this->getLogger();
diff --git a/Tests/Output/AnsiColorModeTest.php b/Tests/Output/AnsiColorModeTest.php
index eb3e463e8..aa1506e26 100644
--- a/Tests/Output/AnsiColorModeTest.php
+++ b/Tests/Output/AnsiColorModeTest.php
@@ -11,23 +11,20 @@
 
 namespace Symfony\Component\Console\Tests\Output;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Exception\InvalidArgumentException;
 use Symfony\Component\Console\Output\AnsiColorMode;
 
 class AnsiColorModeTest extends TestCase
 {
-    /**
-     * @dataProvider provideColorsConversion
-     */
+    #[DataProvider('provideColorsConversion')]
     public function testColorsConversionToAnsi4(string $corlorHex, array $expected)
     {
         $this->assertSame((string) $expected[AnsiColorMode::Ansi4->name], AnsiColorMode::Ansi4->convertFromHexToAnsiColorCode($corlorHex));
     }
 
-    /**
-     * @dataProvider provideColorsConversion
-     */
+    #[DataProvider('provideColorsConversion')]
     public function testColorsConversionToAnsi8(string $corlorHex, array $expected)
     {
         $this->assertSame('8;5;'.$expected[AnsiColorMode::Ansi8->name], AnsiColorMode::Ansi8->convertFromHexToAnsiColorCode($corlorHex));
diff --git a/Tests/Output/OutputTest.php b/Tests/Output/OutputTest.php
index 64e491048..cd3f09680 100644
--- a/Tests/Output/OutputTest.php
+++ b/Tests/Output/OutputTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Output;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Symfony\Component\Console\Output\Output;
@@ -94,9 +95,7 @@ private function generateMessages(): iterable
         yield 'bar';
     }
 
-    /**
-     * @dataProvider provideWriteArguments
-     */
+    #[DataProvider('provideWriteArguments')]
     public function testWriteRawMessage($message, $type, $expectedOutput)
     {
         $output = new TestOutput();
@@ -143,9 +142,7 @@ public function testWriteWithInvalidStyle()
         $this->assertEquals("<bar>foo</bar>\n", $output->output, '->writeln() do nothing when a style does not exist');
     }
 
-    /**
-     * @dataProvider verbosityProvider
-     */
+    #[DataProvider('verbosityProvider')]
     public function testWriteWithVerbosityOption($verbosity, $expected, $msg)
     {
         $output = new TestOutput();
diff --git a/Tests/Question/ChoiceQuestionTest.php b/Tests/Question/ChoiceQuestionTest.php
index 564dee724..613cb621f 100644
--- a/Tests/Question/ChoiceQuestionTest.php
+++ b/Tests/Question/ChoiceQuestionTest.php
@@ -11,14 +11,13 @@
 
 namespace Symfony\Component\Console\Tests\Question;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Question\ChoiceQuestion;
 
 class ChoiceQuestionTest extends TestCase
 {
-    /**
-     * @dataProvider selectUseCases
-     */
+    #[DataProvider('selectUseCases')]
     public function testSelectUseCases($multiSelect, $answers, $expected, $message, $default = null)
     {
         $question = new ChoiceQuestion('A question', [
@@ -104,9 +103,7 @@ public function testNonTrimmable()
         $this->assertSame(['First response ', ' Second response'], $question->getValidator()('First response , Second response'));
     }
 
-    /**
-     * @dataProvider selectAssociativeChoicesProvider
-     */
+    #[DataProvider('selectAssociativeChoicesProvider')]
     public function testSelectAssociativeChoices($providedAnswer, $expectedValue)
     {
         $question = new ChoiceQuestion('A question', [
diff --git a/Tests/Question/ConfirmationQuestionTest.php b/Tests/Question/ConfirmationQuestionTest.php
index bd11047b3..4e3d99fea 100644
--- a/Tests/Question/ConfirmationQuestionTest.php
+++ b/Tests/Question/ConfirmationQuestionTest.php
@@ -11,14 +11,13 @@
 
 namespace Symfony\Component\Console\Tests\Question;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Question\ConfirmationQuestion;
 
 class ConfirmationQuestionTest extends TestCase
 {
-    /**
-     * @dataProvider normalizerUsecases
-     */
+    #[DataProvider('normalizerUsecases')]
     public function testDefaultRegexUsecases($default, $answers, $expected, $message)
     {
         $sut = new ConfirmationQuestion('A question', $default);
diff --git a/Tests/Question/QuestionTest.php b/Tests/Question/QuestionTest.php
index 15d8212b9..0fc26aed1 100644
--- a/Tests/Question/QuestionTest.php
+++ b/Tests/Question/QuestionTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Question;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Question\Question;
 
@@ -44,9 +45,7 @@ public function testGetDefaultDefault()
         self::assertNull($this->question->getDefault());
     }
 
-    /**
-     * @dataProvider providerTrueFalse
-     */
+    #[DataProvider('providerTrueFalse')]
     public function testIsSetHidden(bool $hidden)
     {
         $this->question->setHidden($hidden);
@@ -89,9 +88,7 @@ public function testSetHiddenWithNoAutocompleterCallback()
         $this->assertNull($exception);
     }
 
-    /**
-     * @dataProvider providerTrueFalse
-     */
+    #[DataProvider('providerTrueFalse')]
     public function testIsSetHiddenFallback(bool $hidden)
     {
         $this->question->setHiddenFallback($hidden);
@@ -122,9 +119,7 @@ public static function providerGetSetAutocompleterValues()
         ];
     }
 
-    /**
-     * @dataProvider providerGetSetAutocompleterValues
-     */
+    #[DataProvider('providerGetSetAutocompleterValues')]
     public function testGetSetAutocompleterValues($values, $expectValues)
     {
         $this->question->setAutocompleterValues($values);
@@ -143,9 +138,7 @@ public static function providerSetAutocompleterValuesInvalid()
         ];
     }
 
-    /**
-     * @dataProvider providerSetAutocompleterValuesInvalid
-     */
+    #[DataProvider('providerSetAutocompleterValuesInvalid')]
     public function testSetAutocompleterValuesInvalid($values)
     {
         self::expectException(\TypeError::class);
@@ -236,9 +229,7 @@ public static function providerGetSetValidator()
         ];
     }
 
-    /**
-     * @dataProvider providerGetSetValidator
-     */
+    #[DataProvider('providerGetSetValidator')]
     public function testGetSetValidator($callback)
     {
         $this->question->setValidator($callback);
@@ -255,9 +246,7 @@ public static function providerGetSetMaxAttempts()
         return [[1], [5], [null]];
     }
 
-    /**
-     * @dataProvider providerGetSetMaxAttempts
-     */
+    #[DataProvider('providerGetSetMaxAttempts')]
     public function testGetSetMaxAttempts($attempts)
     {
         $this->question->setMaxAttempts($attempts);
@@ -269,9 +258,7 @@ public static function providerSetMaxAttemptsInvalid()
         return [[0], [-1]];
     }
 
-    /**
-     * @dataProvider providerSetMaxAttemptsInvalid
-     */
+    #[DataProvider('providerSetMaxAttemptsInvalid')]
     public function testSetMaxAttemptsInvalid($attempts)
     {
         self::expectException(\InvalidArgumentException::class);
@@ -297,9 +284,7 @@ public function testGetNormalizerDefault()
         self::assertNull($this->question->getNormalizer());
     }
 
-    /**
-     * @dataProvider providerTrueFalse
-     */
+    #[DataProvider('providerTrueFalse')]
     public function testSetMultiline(bool $multiline)
     {
         self::assertSame($this->question, $this->question->setMultiline($multiline));
diff --git a/Tests/SignalRegistry/SignalMapTest.php b/Tests/SignalRegistry/SignalMapTest.php
index 3a0c49bb0..9bc7f34a2 100644
--- a/Tests/SignalRegistry/SignalMapTest.php
+++ b/Tests/SignalRegistry/SignalMapTest.php
@@ -11,14 +11,13 @@
 
 namespace Symfony\Component\Console\Tests\SignalRegistry;
 
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\SignalRegistry\SignalMap;
 
 class SignalMapTest extends TestCase
 {
-    /**
-     * @requires extension pcntl
-     */
+    #[RequiresPhpExtension('pcntl')]
     public function testSignalExists()
     {
         $this->assertSame('SIGINT', SignalMap::getSignalName(\SIGINT));
diff --git a/Tests/SignalRegistry/SignalRegistryTest.php b/Tests/SignalRegistry/SignalRegistryTest.php
index 92d500f9e..51146b3f2 100644
--- a/Tests/SignalRegistry/SignalRegistryTest.php
+++ b/Tests/SignalRegistry/SignalRegistryTest.php
@@ -11,12 +11,11 @@
 
 namespace Symfony\Component\Console\Tests\SignalRegistry;
 
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\SignalRegistry\SignalRegistry;
 
-/**
- * @requires extension pcntl
- */
+#[RequiresPhpExtension('pcntl')]
 class SignalRegistryTest extends TestCase
 {
     protected function tearDown(): void
diff --git a/Tests/Style/SymfonyStyleTest.php b/Tests/Style/SymfonyStyleTest.php
index a3b7ae406..8f82733e6 100644
--- a/Tests/Style/SymfonyStyleTest.php
+++ b/Tests/Style/SymfonyStyleTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Style;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Exception\RuntimeException;
@@ -47,9 +48,7 @@ protected function tearDown(): void
         putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS');
     }
 
-    /**
-     * @dataProvider inputCommandToOutputFilesProvider
-     */
+    #[DataProvider('inputCommandToOutputFilesProvider')]
     public function testOutputs($inputCommandFilepath, $outputFilepath)
     {
         $code = require $inputCommandFilepath;
@@ -58,9 +57,7 @@ public function testOutputs($inputCommandFilepath, $outputFilepath)
         $this->assertStringEqualsFile($outputFilepath, $this->tester->getDisplay(true));
     }
 
-    /**
-     * @dataProvider inputInteractiveCommandToOutputFilesProvider
-     */
+    #[DataProvider('inputInteractiveCommandToOutputFilesProvider')]
     public function testInteractiveOutputs($inputCommandFilepath, $outputFilepath)
     {
         $code = require $inputCommandFilepath;
diff --git a/Tests/TerminalTest.php b/Tests/TerminalTest.php
index d43469d12..4d417dfba 100644
--- a/Tests/TerminalTest.php
+++ b/Tests/TerminalTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Output\AnsiColorMode;
 use Symfony\Component\Console\Terminal;
@@ -95,9 +96,7 @@ public function testSttyOnWindows()
         $this->assertSame((int) $matches[1], $terminal->getWidth());
     }
 
-    /**
-     * @dataProvider provideTerminalColorEnv
-     */
+    #[DataProvider('provideTerminalColorEnv')]
     public function testGetColorMode(?string $testColorTerm, ?string $testTerm, AnsiColorMode $expected)
     {
         $oriColorTerm = getenv('COLORTERM');
diff --git a/Tests/Tester/CommandTesterTest.php b/Tests/Tester/CommandTesterTest.php
index cfdebe4d8..b7490ad3f 100644
--- a/Tests/Tester/CommandTesterTest.php
+++ b/Tests/Tester/CommandTesterTest.php
@@ -23,6 +23,8 @@
 use Symfony\Component\Console\Question\Question;
 use Symfony\Component\Console\Style\SymfonyStyle;
 use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\Console\Tests\Fixtures\InvokableExtendingCommandTestCommand;
+use Symfony\Component\Console\Tests\Fixtures\InvokableTestCommand;
 
 class CommandTesterTest extends TestCase
 {
@@ -104,7 +106,7 @@ public function testCommandFromApplication()
             return 0;
         });
 
-        $application->add($command);
+        $application->addCommand($command);
 
         $tester = new CommandTester($application->find('foo'));
 
@@ -267,4 +269,24 @@ public function testErrorOutput()
 
         $this->assertSame('foo', $tester->getErrorOutput());
     }
+
+    public function testAInvokableCommand()
+    {
+        $command = new InvokableTestCommand();
+
+        $tester = new CommandTester($command);
+        $tester->execute([]);
+
+        $tester->assertCommandIsSuccessful();
+    }
+
+    public function testAInvokableExtendedCommand()
+    {
+        $command = new InvokableExtendingCommandTestCommand();
+
+        $tester = new CommandTester($command);
+        $tester->execute([]);
+
+        $tester->assertCommandIsSuccessful();
+    }
 }
diff --git a/Tests/Tester/Constraint/CommandIsSuccessfulTest.php b/Tests/Tester/Constraint/CommandIsSuccessfulTest.php
index 61ab5d0f8..415f3861f 100644
--- a/Tests/Tester/Constraint/CommandIsSuccessfulTest.php
+++ b/Tests/Tester/Constraint/CommandIsSuccessfulTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Console\Tests\Tester\Constraint;
 
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Command\Command;
@@ -27,9 +28,7 @@ public function testConstraint()
         $this->assertFalse($constraint->evaluate(Command::INVALID, '', true));
     }
 
-    /**
-     * @dataProvider providesUnsuccessful
-     */
+    #[DataProvider('providesUnsuccessful')]
     public function testUnsuccessfulCommand(string $expectedException, int $exitCode)
     {
         $constraint = new CommandIsSuccessful();
diff --git a/Tests/phpt/alarm/command_exit.phpt b/Tests/phpt/alarm/command_exit.phpt
index c2cf3edc7..a53af8567 100644
--- a/Tests/phpt/alarm/command_exit.phpt
+++ b/Tests/phpt/alarm/command_exit.phpt
@@ -53,7 +53,7 @@ class MyCommand extends Command
 
 $app = new Application();
 $app->setDispatcher(new \Symfony\Component\EventDispatcher\EventDispatcher());
-$app->add(new MyCommand('foo'));
+$app->addCommand(new MyCommand('foo'));
 
 $app
     ->setDefaultCommand('foo', true)
diff --git a/Tests/phpt/signal/command_exit.phpt b/Tests/phpt/signal/command_exit.phpt
index e14f80c47..e653d65c1 100644
--- a/Tests/phpt/signal/command_exit.phpt
+++ b/Tests/phpt/signal/command_exit.phpt
@@ -45,7 +45,7 @@ class MyCommand extends Command
 
 $app = new Application();
 $app->setDispatcher(new \Symfony\Component\EventDispatcher\EventDispatcher());
-$app->add(new MyCommand('foo'));
+$app->addCommand(new MyCommand('foo'));
 
 $app
     ->setDefaultCommand('foo', true)
diff --git a/composer.json b/composer.json
index 65d69913a..f4680e834 100644
--- a/composer.json
+++ b/composer.json
@@ -16,35 +16,27 @@
         }
     ],
     "require": {
-        "php": ">=8.2",
-        "symfony/deprecation-contracts": "^2.5|^3",
-        "symfony/polyfill-mbstring": "~1.0",
+        "php": ">=8.4",
+        "symfony/polyfill-mbstring": "^1.0",
         "symfony/service-contracts": "^2.5|^3",
-        "symfony/string": "^7.2"
+        "symfony/string": "^7.4|^8.0"
     },
     "require-dev": {
-        "symfony/config": "^6.4|^7.0",
-        "symfony/event-dispatcher": "^6.4|^7.0",
-        "symfony/http-foundation": "^6.4|^7.0",
-        "symfony/http-kernel": "^6.4|^7.0",
-        "symfony/dependency-injection": "^6.4|^7.0",
-        "symfony/lock": "^6.4|^7.0",
-        "symfony/messenger": "^6.4|^7.0",
-        "symfony/process": "^6.4|^7.0",
-        "symfony/stopwatch": "^6.4|^7.0",
-        "symfony/var-dumper": "^6.4|^7.0",
-        "psr/log": "^1|^2|^3"
+        "psr/log": "^1|^2|^3",
+        "symfony/config": "^7.4|^8.0",
+        "symfony/dependency-injection": "^7.4|^8.0",
+        "symfony/event-dispatcher": "^7.4|^8.0",
+        "symfony/http-foundation": "^7.4|^8.0",
+        "symfony/http-kernel": "^7.4|^8.0",
+        "symfony/lock": "^7.4|^8.0",
+        "symfony/messenger": "^7.4|^8.0",
+        "symfony/process": "^7.4|^8.0",
+        "symfony/stopwatch": "^7.4|^8.0",
+        "symfony/var-dumper": "^7.4|^8.0"
     },
     "provide": {
         "psr/log-implementation": "1.0|2.0|3.0"
     },
-    "conflict": {
-        "symfony/dependency-injection": "<6.4",
-        "symfony/dotenv": "<6.4",
-        "symfony/event-dispatcher": "<6.4",
-        "symfony/lock": "<6.4",
-        "symfony/process": "<6.4"
-    },
     "autoload": {
         "psr-4": { "Symfony\\Component\\Console\\": "" },
         "exclude-from-classmap": [
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 0e96921be..950f1c096 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/9.3/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.3/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
+         failOnDeprecation="true"
          failOnRisky="true"
          failOnWarning="true"
 >
@@ -18,7 +19,7 @@
         </testsuite>
     </testsuites>
 
-    <coverage>
+    <source ignoreSuppressionOfDeprecations="true">
         <include>
             <directory>./</directory>
         </include>
@@ -27,15 +28,11 @@
             <directory>./Tests</directory>
             <directory>./vendor</directory>
         </exclude>
-    </coverage>
+    </source>
 
-    <listeners>
-        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
-            <arguments>
-                <array>
-                    <element key="time-sensitive"><string>Symfony\Component\Console</string></element>
-                </array>
-            </arguments>
-        </listener>
-    </listeners>
+    <extensions>
+        <bootstrap class="Symfony\Bridge\PhpUnit\SymfonyExtension">
+            <parameter name="clock-mock-namespaces" value="Symfony\Component\Console" />
+        </bootstrap>
+    </extensions>
 </phpunit>
