Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosIntel Devs: Smart AI Edge Solutions - 0 Introduction | Intel Software(22.09.2026 um 23:48 Uhr)
Windows Tipps & SecurityGoogles gibt Chrome 154 frei und schließt über 100 Lücken(23.09.2026 um 09:11 Uhr)
Windows Tipps & SecurityKlangerlebnis & Sicherheit im Vonovia Ruhrstadion(23.09.2026 um 08:45 Uhr)
Unix & Linux ServerLocal AI Jargon Quiz: Do You Know All These Buzzwords?(23.09.2026 um 08:38 Uhr)
Sichere ProgrammierungNeu von AWS: Weniger Kontextpflege für selbst gebaute KI-Agenten(23.09.2026 um 09:52 Uhr)
Sichere ProgrammierungLINQ GroupBy: The Operator Everyone Uses Wrong(23.09.2026 um 09:41 Uhr)
Sichere ProgrammierungIT Heard About the Acquisition Nine Days Before It Closed(23.09.2026 um 09:45 Uhr)
YouTube Security VideosIntel Devs: Smart AI Edge Solutions - 0 Introduction | Intel Software(22.09.2026 um 23:48 Uhr)
Windows Tipps & SecurityGoogles gibt Chrome 154 frei und schließt über 100 Lücken(23.09.2026 um 09:11 Uhr)
Windows Tipps & SecurityKlangerlebnis & Sicherheit im Vonovia Ruhrstadion(23.09.2026 um 08:45 Uhr)
Unix & Linux ServerLocal AI Jargon Quiz: Do You Know All These Buzzwords?(23.09.2026 um 08:38 Uhr)
Sichere ProgrammierungNeu von AWS: Weniger Kontextpflege für selbst gebaute KI-Agenten(23.09.2026 um 09:52 Uhr)
Sichere ProgrammierungLINQ GroupBy: The Operator Everyone Uses Wrong(23.09.2026 um 09:41 Uhr)
Sichere ProgrammierungIT Heard About the Acquisition Nine Days Before It Closed(23.09.2026 um 09:45 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Symfony 7.4: The Ultimate Guide to Modern Invokable Commands

The evolution of the Symfony Console component has been a journey of consistent refinement. For years, developers grew accustomed to the ritual of extending the Command class, implementing the configure() method to define arguments and…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

The evolution of the Symfony Console component has been a journey of consistent refinement. For years, developers grew accustomed to the ritual of extending the Command class, implementing the configure() method to define arguments and options and placing their logic inside execute(). It was robust, deterministic and verbose.



With the advent of Symfony 5 and 6, we saw the introduction of Invokable Commands — a paradigm shift that allowed us to treat commands more like controllers. The __invoke() method became the new entry point and the boilerplate of configure() began to fade, replaced partially by PHP attributes like #[AsCommand]. However, one friction point remained: the disconnect between the command’s signature and the actual input parsing. We still found ourselves manually fetching arguments via $input->getArgument(‘…’) or relying on complex configurations to map inputs to typed variables.






Symfony 7.4 changes everything



Released in November 2025, Symfony 7.4 introduces a suite of quality-of-life improvements for the Console component that effectively bridges the gap between Console Commands and Http Controllers. With native support for Backed Enums, Input DTOs via #[MapInput] and declarative interactivity with #[Interact] and #[Ask], writing CLI tools has never been this type-safe or expressive.



In this comprehensive guide, we will explore these new features in depth. We will refactor a legacy command into a modern Symfony 7.4 masterpiece, covering installation, implementation, verification and testing.






Prerequisites and Installation



Before diving into the code, ensure your environment is ready. You will need:




  1. PHP 8.2 or higher (PHP 8.4 recommended for better syntax support).

  2. Composer installed globally.



To follow along with the examples, creating a new Symfony 7.4 project or upgrading an existing one is necessary.



If you are starting fresh:




composer create-project symfony/skeleton:^7.4 my_cli_app
cd my_cli_app
composer require symfony/console:^7.4






If you are upgrading an existing project, ensure your composer.json explicitly targets the 7.4 release for the console component:




{
"require": {
"php": ">=8.2",
"symfony/console": "^7.4",
"symfony/framework-bundle": "^7.4",
"symfony/runtime": "^7.4"
}
}






Run the update command:




composer update symfony/*






Verify your version:




php bin/console --version
# Output should look like: Symfony 7.4.x (env: dev, debug: true)









Native Enum Support






The Old Way (Pre-7.4)



Previously, handling enumerated values in commands was a manual process. You would accept a string argument, valid it against a list of allowed values manually and then perhaps map it to a PHP Enum usage.




// src/Command/LegacyServerCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:legacy-server')]
class LegacyServerCommand extends Command
{
protected function configure(): void
{
$this->addArgument('region', InputArgument::REQUIRED, 'The server region (us, eu)');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$region = $input->getArgument('region');

if (!in_array($region, ['us', 'eu'])) {
$output->writeln('<error>Invalid region.</error>');
return Command::FAILURE;
}

// Logic here...
return Command::SUCCESS;
}
}






This works, but it leaks validation logic into the execution flow and lacks type safety.






The Symfony 7.4 Way



In Symfony 7.4, the Console component’s ArgumentResolver logic has been ported to commands. You can now type-hint arguments with Backed Enums. Symfony will automatically:




  1. Read the input string.

  2. Try to map it to the Enum’s backing value.

  3. Throw a descriptive error if the value is invalid, listing available options.



Let’s define our Enum first.




// src/Enum/ServerRegion.php
namespace App\Enum;

enum ServerRegion: string
{
case US = 'us-east-1';
case EU = 'eu-central-1';
case ASIA = 'ap-northeast-1';
}






Now, the command becomes incredibly simple:




// src/Command/CreateServerCommand.php
namespace App\Command;

use App\Enum\ServerRegion;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Argument; // Note: In 7.4 attributes are often simplified
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:create-server', description: 'Creates a server in a specific region.')]
class CreateServerCommand extends Command
{
// The magic happens here: Type-hinting the Enum
public function __invoke(
OutputInterface $output,
#[Argument] ServerRegion $region
): int {
$output->writeln(sprintf('Creating server in region: %s', $region->value));

return Command::SUCCESS;
}
}






Try running the command with an invalid value:




php bin/console app:create-server mars






Output:




[ERROR] The value "mars" is not allowed for argument "region".
Allowed values are: "us-east-1", "eu-central-1", "ap-northeast-1".






This validation comes “for free,” strictly enforced by the framework before your code even executes.






Input DTOs with #[MapInput]



As your commands grow, your __invoke method can become cluttered with dozens of arguments and options. This is the “Long Parameter List” code smell. Controllers solved this with #[MapRequestPayload] and now Console follows suit with #[MapInput].



This allows you to extract your command’s input definition into a dedicated Data Transfer Object (DTO).






The DTO Class



Create a plain PHP class to hold your input data. Use standard validation constraints if you have the Validator component installed!




// src/Dto/ServerInput.php
namespace App\Dto;

use App\Enum\ServerRegion;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Validator\Constraints as Assert;

class ServerInput
{
#[Argument(description: 'The region to deploy to')]
public ServerRegion $region;

#[Option(description: 'The size of the instance')]
#[Assert\Choice(['small', 'medium', 'large'])]
public string $size = 'small';

#[AsOption(name: 'dry-run', description: 'Simulate the creation')]
public bool $dryRun = false;
}









The Refactored Command



We now inject this DTO into the command using the #[MapInput] attribute.




// src/Command/DeployServerCommand.php
namespace App\Command;

use App\Dto\ServerInput;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\MapInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:deploy-server')]
class DeployServerCommand extends Command
{
public function __invoke(
OutputInterface $output,
#[MapInput] ServerInput $input
): int {
if ($input->dryRun) {
$output->writeln('<info>Dry run enabled. No changes made.</info>');
}

$output->writeln(sprintf(
'Deploying %s instance to %s...',
$input->size,
$input->region->value
));

return Command::SUCCESS;
}
}









Why this matters





  1. Reusability: You can reuse the ServerInput DTO in other services or even controllers if mapped correctly.


  2. Readability: The command logic is separated from the configuration of arguments and options.


  3. Validation: If you use symfony/validator, the DTO is validated automatically before __invoke is called.






Declarative Interaction with #[Interact] and #[Ask]



Interactive commands are vital for good DX, but implementing the interact() method often felt like writing a second command just to fill in the blanks of the first one. Symfony 7.4 introduces attributes to handle this declaratively.






The #[Ask] Attribute



For simple cases where a missing argument should prompt the user, use #[Ask].




// src/Command/HelloCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Ask; // New in 7.4
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:hello')]
class HelloCommand extends Command
{
public function __invoke(
OutputInterface $output,
#[Argument, Ask(question: "What is your name?")]
string $name
): int {
$output->writeln("Hello, $name!");
return Command::SUCCESS;
}
}






If the user runs php bin/console app:hello, the command will pause and ask “What is your name?”. If they run php bin/console app:hello World, it skips the prompt.






The #[Interact] Attribute



For complex interactions (e.g., dynamic questions based on previous answers), you can now mark any method as an interaction handler, injecting InputInterface and StyleInterface automatically.




// src/Command/WizardCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Interact;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:wizard')]
class WizardCommand extends Command
{
protected function configure(): void
{
$this->addArgument('password', InputArgument::REQUIRED);
}

// This method is called automatically before __invoke if arguments are missing
#[Interact]
public function promptForPassword(InputInterface $input, SymfonyStyle $io): void
{
if (null === $input->getArgument('password')) {
$password = $io->askHidden('Please enter your API password');
$input->setArgument('password', $password);
}
}

public function __invoke(OutputInterface $output, string $password): int
{
$output->writeln('Password received (hashed): ' . md5($password));
return Command::SUCCESS;
}
}









Testing Invokable Commands



Symfony 7.4 ensures that CommandTester works seamlessly with these new abstractions. Testing commands that use DTOs or Enums requires no special setup.






The Test Case



We’ll test the DeployServerCommand we created earlier.




// tests/Command/DeployServerCommandTest.php
namespace App\Tests\Command;

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

class DeployServerCommandTest extends KernelTestCase
{
public function testExecuteWithDto(): void
{
$kernel = self::bootKernel();
$application = new Application($kernel);

$command = $application->find('app:deploy-server');
$commandTester = new CommandTester($command);

$commandTester->execute([
'region' => 'us-east-1', // Passing string, automatically converted to Enum
'--size' => 'large',
'--dry-run' => true,
]);

$commandTester->assertCommandIsSuccessful();

$output = $commandTester->getDisplay();
$this->assertStringContainsString('Dry run enabled', $output);
$this->assertStringContainsString('Deploying large instance to us-east-1', $output);
}

public function testInvalidEnumThrowsError(): void
{
$kernel = self::bootKernel();
$application = new Application($kernel);
$command = $application->find('app:deploy-server');
$commandTester = new CommandTester($command);

// We expect a runtime exception or validation error depending on configuration
// In Console context, this usually results in a status code 1 and error output
$this->expectException(\Throwable::class);
// Or inspect status code:
// $exitCode = $commandTester->execute(['region' => 'mars']);
// $this->assertNotSame(0, $exitCode);

$commandTester->execute([
'region' => 'mars',
]);
}
}






Run your tests:




php bin/phpunit tests/Command/DeployServerCommandTest.php









Real-World Scenario: A Complex Report Generator



Let’s combine all features — Enums, DTOs and Attributes — into a cohesive, professional-grade command. Imagine a command that generates financial reports.






The Enums






namespace App\Enum;

enum ReportFormat: string {
case PDF = 'pdf';
case CSV = 'csv';
case JSON = 'json';
}

enum ReportPeriod: string {
case DAILY = 'daily';
case WEEKLY = 'weekly';
case MONTHLY = 'monthly';
}









The Input DTO






namespace App\Dto;

use App\Enum\ReportFormat;
use App\Enum\ReportPeriod;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Attribute\Ask;

class ReportInput
{
#[Argument(description: 'Type of report')]
#[Ask('What type of report would you like to generate?')]
public string $reportType;

#[Option]
public ReportPeriod $period = ReportPeriod::WEEKLY;

#[Option]
public ReportFormat $format = ReportFormat::PDF;

#[AsOption(name: 'email', description: 'Email to send report to')]
public ?string $recipientEmail = null;
}









The Service



Ideally, your logic is in a service, not the command.




namespace App\Service;

use App\Dto\ReportInput;

class ReportGenerator
{
public function generate(ReportInput $input): void
{
// ... generation logic
}
}









The Command






namespace App\Command;

use App\Dto\ReportInput;
use App\Service\ReportGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Interact;
use Symfony\Component\Console\Attribute\MapInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:report:generate')]
class GenerateReportCommand extends Command
{
public function __construct(
private ReportGenerator $generator
) {
parent::__construct();
}

#[Interact]
public function interactRecipient(InputInterface $input, SymfonyStyle $io): void
{
// Only ask for email if not provided and format is PDF (business rule)
// Accessing raw input here since mapping happens later
if (null === $input->getOption('email') && 'pdf' === $input->getOption('format')) {
$email = $io->ask('Enter recipient email for the PDF');
$input->setOption('email', $email);
}
}

public function __invoke(
OutputInterface $output,
#[MapInput] ReportInput $input
): int {
$output->writeln("Starting {$input->period->value} report generation...");

$this->generator->generate($input);

$output->writeln("Report generated in {$input->format->value} format.");

if ($input->recipientEmail) {
$output->writeln("Sent to: {$input->recipientEmail}");
}

return Command::SUCCESS;
}
}






This example demonstrates the power of Symfony 7.4:





  1. Separation of Concerns: The ReportInput DTO handles data structure. The Service handles logic. The Command handles the CLI interface.


  2. Context-Aware Interactivity: The #[Interact] method allows for dynamic questions (only asking for email if PDF is selected) that pure attributes can’t easily handle.


  3. Type Safety: We never manually validate if $format is pdf or csv. The Enum casting guarantees it.






Conclusion



Symfony 7.4 marks a significant maturity point for the Console component. By adopting attributes and DTOs, the framework acknowledges that Console commands are first-class citizens in modern applications, deserving the same developer experience (DX) as HTTP Controllers.



Key Takeaways:





  1. Stop parsing, start declaring: Use #[MapInput] and Enums to let Symfony handle data hydration and validation.


  2. Clean up your signatures: Move long lists of arguments into DTOs.


  3. Embrace declarative interaction: Use #[Ask] for simple prompts and #[Interact] for complex flows without overriding the parent class logic.



These changes reduce boilerplate, increase testability and make your code significantly easier to read. If you haven’t upgraded to Symfony 7.4 yet, the improved Console component alone is a compelling reason to make the jump.






Let’s be in touch



Are you ready to modernize your CLI tools? Start by refactoring your most complex command using #[MapInput] and share your experience.



If you found this guide helpful or have questions about specific edge cases, be in touch! You can find me on LinkedIn. Subscribe to the newsletter for more deep dives into Symfony’s ecosystem.



Happy Coding!

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Symfony 7.4: The Ultimate Guide to Modern Invokable Commands

Thematisch verwandte Begriffe: Symfony, Ultimate, Guide, Modern · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96258 | A vulnerability has been found in onSite internet GmbH Auktion NG Auktio…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick