Robuste CLI-Skripte mit Symfony Console bauen
AI generated
<?php
8.4
PHP · CLI · Symfony Console · Automatisierung
Robuste CLI-Skripte mit Symfony Console bauen
vom rohen $argv-Skript zum getesteten Tool

Rohe PHP-CLI-Skripte mit $argv stoßen schnell an Grenzen: keine Validierung, kein Hilfetext, keine Tests. Die Symfony-Console-Komponente lässt sich als eigenständiges Composer-Paket in jedem PHP-Projekt einsetzen und liefert Command-Klassen, Prompts, Fortschrittsanzeigen und Exit-Codes als solides Fundament.

14 Min. Lesezeit Command · InputArgument · CommandTester PHP 8.4

1. Warum rohe PHP-CLI-Skripte mit $argv an Grenzen stoßen

Fast jedes PHP-Projekt beginnt seine CLI-Historie mit einem einfachen Skript, das $argv von Hand auswertet: $argv[1] ist der Dateiname, $argv[2] vielleicht eine Umgebung, $argv[3] ein Flag. Das funktioniert für den ersten Anwendungsfall, wird aber mit jedem zusätzlichen Parameter fragiler. Es gibt keine eingebaute Validierung, ob ein Pflichtargument überhaupt übergeben wurde, keine Typprüfung, keine sinnvolle Fehlermeldung, wenn die Reihenfolge der Parameter nicht stimmt. Der Aufrufer muss die interne Struktur des Skripts kennen, weil nichts davon dokumentiert ist.

Ein zweites, oft unterschätztes Problem ist der fehlende Hilfetext. Wer ein rohes $argv-Skript nach sechs Monaten wieder aufruft, findet üblicherweise keine --help-Option, sondern muss den Quellcode öffnen, um herauszufinden, welche Parameter erwartet werden und in welcher Reihenfolge. Auch Dinge wie farbige Ausgabe für Erfolg und Fehler, saubere Exit-Codes für Monitoring-Systeme oder interaktive Rückfragen bei fehlenden Angaben müssen komplett von Hand nachgebaut werden. Jedes Skript im Projekt erfindet dabei seine eigenen, leicht unterschiedlichen Konventionen.

Am schwersten wiegt aber die fehlende Testbarkeit. Ein $argv-Skript lässt sich nur schwer isoliert testen, weil Ein- und Ausgabe eng mit den PHP-Superglobalen und mit echo-Aufrufen verwoben sind. Die Symfony Console-Komponente löst genau diese Probleme, ohne dass das komplette Symfony-Framework installiert werden muss. Als eigenständiges Composer-Paket bringt sie Command-Klassen, Argument-Validierung, Hilfetext, Prompts und einen Test-Helper mit, der CLI-Logik ohne echten Terminal-Aufruf prüfbar macht.

Merkmal Rohes PHP-Skript ($argv) Symfony Console
Argument-Validierung manuell, fehleranfällig InputArgument, automatisch geprüft
Hilfetext (--help) nicht vorhanden automatisch generiert
Farbige Ausgabe manuelle ANSI-Codes SymfonyStyle, eingebaut
Testbarkeit kaum isolierbar CommandTester, PHPUnit-tauglich
Exit-Codes von Hand mit exit() Command::SUCCESS/FAILURE/INVALID
Interaktive Prompts eigene readline-Logik QuestionHelper, Validierung inklusive
Auto-Completion nicht vorhanden CompletionInput je Command

2. Grundgerüst mit der Symfony-Console-Komponente

Der Einstieg in Symfony Console gelingt mit einer einzigen Composer-Abhängigkeit: composer require symfony/console. Damit ist keine Kopplung an ein bestehendes Framework nötig, das Paket lässt sich in jedem PHP-8.4-Projekt einsetzen, ob mit Symfony, Laravel oder komplett ohne Framework. Zentrales Objekt ist die Application-Klasse, die als Container für alle registrierten Commands dient und das eigentliche Parsen der Kommandozeile übernimmt.

Ein einzelner Befehl wird als eigene Klasse modelliert, die von Symfony\Component\Console\Command\Command erbt. Die Methode configure() beschreibt Name, Beschreibung und alle Argumente und Optionen des Commands, während execute() die eigentliche Logik enthält und einen Exit-Code zurückgibt. Diese klare Trennung zwischen Deklaration und Ausführung macht Commands sofort lesbar, auch ohne die restliche Codebasis zu kennen.

Wichtig für CLI-Skripte in produktiven Projekten ist außerdem, dass eine Application beliebig viele Commands verwalten kann. Statt dutzender einzelner PHP-Dateien im Projekt-Root entsteht ein einziger Einstiegspunkt, typischerweise bin/console, über den alle Befehle mit einheitlicher Hilfe, einheitlichem Exit-Code-Verhalten und einheitlicher Fehlerausgabe erreichbar sind.


<?php

declare(strict_types=1);

namespace App\Command;

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

// A minimal, self-contained command class
#[AsCommand(name: 'app:import', description: 'Import products from a CSV file')]
final class ImportCommand extends Command
{
    protected function configure(): void
    {
        $this->setHelp('This command imports products from a given CSV file into the catalog.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Starting product import...');

        // Business logic goes here
        $output->writeln('Import finished.');

        return Command::SUCCESS;
    }
}

<?php

declare(strict_types=1);

// bin/console: single entry point for all CLI commands
require __DIR__ . '/../vendor/autoload.php';

use App\Command\ImportCommand;
use Symfony\Component\Console\Application;

$application = new Application('My CLI Tool', '1.0.0');
$application->add(new ImportCommand());
$application->run();

3. Argumente und Optionen definieren

Die Stärke von Symfony Console liegt in der klaren Trennung zwischen Argumenten und Optionen. Ein InputArgument ist ein positionsbasierter Wert, etwa der Pfad zu einer Datei, der ohne Präfix übergeben wird. Eine InputOption hingegen wird mit --name oder einem Kurzflag wie -n angegeben und ist standardmäßig optional. Beide lassen sich als verpflichtend (InputArgument::REQUIRED), optional (InputArgument::OPTIONAL) oder als Array (InputArgument::IS_ARRAY) deklarieren, wenn ein Argument mehrfach übergeben werden soll.

Optionen kennen zusätzlich den Modus InputOption::VALUE_NONE für reine Flags ohne Wert, etwa --dry-run, sowie InputOption::VALUE_REQUIRED und InputOption::VALUE_OPTIONAL für Optionen mit Wert. Auch bei Optionen ist ein Array-Modus möglich, sodass ein Flag wie --tag=foo --tag=bar mehrfach angegeben und als Liste eingesammelt werden kann. Diese Kombinatorik deckt praktisch jeden CLI-Anwendungsfall ab, ohne dass eigene Parser-Logik geschrieben werden muss.

Innerhalb von execute() werden die Werte typsicher über $input->getArgument() und $input->getOption() abgerufen. Fehlt ein als REQUIRED markiertes Argument beim Aufruf, bricht Symfony Console bereits vor dem Betreten von execute() mit einer verständlichen Fehlermeldung und dem passenden Exit-Code ab, ganz ohne zusätzlichen Code im Command selbst.


<?php

declare(strict_types=1);

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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:import', description: 'Import products from one or more CSV files')]
final class ImportCommand extends Command
{
    protected function configure(): void
    {
        $this
            // Required positional argument
            ->addArgument('source', InputArgument::REQUIRED, 'Path to the CSV file')
            // Optional argument with a default value
            ->addArgument('store', InputArgument::OPTIONAL, 'Target store code', 'default')
            // Array argument: zero or more values
            ->addArgument('tags', InputArgument::IS_ARRAY, 'Tags to attach to imported products')
            // Value-less flag
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simulate the import without writing data')
            // Option with a required value and default
            ->addOption('batch-size', 'b', InputOption::VALUE_REQUIRED, 'Number of rows per batch', '100')
            // Repeatable option collected into an array
            ->addOption('skip-column', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Columns to skip');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $source = $input->getArgument('source');
        $batchSize = (int) $input->getOption('batch-size');
        $isDryRun = (bool) $input->getOption('dry-run');

        $output->writeln(sprintf('Importing %s in batches of %d (dry-run: %s)', $source, $batchSize, $isDryRun ? 'yes' : 'no'));

        return Command::SUCCESS;
    }
}

4. Interaktive Prompts und Validierung mit SymfonyStyle

Nicht jeder Parameter lässt sich sinnvoll über Argumente und Optionen vorwegnehmen, besonders bei destruktiven Operationen möchte man eine explizite Bestätigung im Terminal einholen. Symfony Console bietet dafür die Klasse SymfonyStyle, die einen einheitlichen visuellen Stil für alle Ausgaben liefert und gleichzeitig Zugriff auf die wichtigsten Prompt-Typen bereitstellt: ask() für Freitext, confirm() für Ja-Nein-Entscheidungen und choice() für eine Auswahl aus vordefinierten Optionen.

Besonders wertvoll ist die eingebaute Validierung. Der ask()-Methode kann eine Callback-Funktion übergeben werden, die den eingegebenen Wert prüft und bei ungültiger Eingabe eine Exception wirft. Symfony Console fängt diese Exception ab, zeigt die Fehlermeldung an und fragt automatisch erneut, ohne dass der aufrufende Code eine eigene Schleife schreiben muss. Das reduziert die typische Boilerplate-Logik für Eingabeprüfung auf eine einzige Zeile pro Regel.

Für sensible Eingaben wie Passwörter unterstützt ask() zusätzlich einen versteckten Eingabemodus, bei dem die Tastatureingabe im Terminal nicht sichtbar ist. In Kombination mit confirm() für kritische Aktionen, etwa das Löschen von Datensätzen, entsteht so ein CLI-Skript, das sich in puncto Bedienkomfort kaum von einer interaktiven Anwendung unterscheidet, ohne dass eine grafische Oberfläche nötig wäre.


<?php

declare(strict_types=1);

namespace App\Command;

use InvalidArgumentException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class SetupCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        // Free text prompt with a validation callback
        $email = $io->ask('Admin email address', null, function (?string $value): string {
            if ($value === null || !str_contains($value, '@')) {
                throw new InvalidArgumentException('Please provide a valid email address.');
            }

            return $value;
        });

        // Selection prompt with a predefined set of options
        $environment = $io->choice('Target environment', ['development', 'staging', 'production'], 'development');

        // Confirmation before a destructive action
        if ($environment === 'production' && !$io->confirm('This will run against production. Continue?', false)) {
            $io->warning('Aborted by user.');

            return Command::FAILURE;
        }

        $io->success(sprintf('Configured admin "%s" for "%s".', $email, $environment));

        return Command::SUCCESS;
    }
}

5. Fortschrittsanzeigen und Tabellen

Sobald ein CLI-Skript größere Datenmengen verarbeitet, ob Produktimporte, Bildkonvertierungen oder Cache-Aufwärmvorgänge, wird eine Fortschrittsanzeige zum entscheidenden Feedback-Kanal für den Betreiber. Symfony Console bringt dafür die Klasse ProgressBar mit, die mit einer bekannten Gesamtanzahl an Schritten initialisiert und dann in einer Schleife mit advance() weitergeschaltet wird. Die Bar zeigt neben dem Fortschrittsbalken auch verstrichene Zeit und geschätzte Restdauer an, ganz ohne dass eigene Berechnungslogik nötig wäre.

Für tabellarische Ausgaben, etwa eine Übersicht verarbeiteter Datensätze oder eine Zusammenfassung am Ende eines Laufs, steht der Table-Helper zur Verfügung. Er übernimmt Spaltenbreiten, Rahmen und Zeilenumbrüche automatisch und erzeugt so lesbare Ausgaben, die sich an die tatsächliche Terminalbreite anpassen. Beide Helper lassen sich problemlos kombinieren: Während des Verarbeitens läuft die ProgressBar, am Ende fasst eine Table das Ergebnis zusammen.

Ein wichtiges Detail bei der Verwendung von ProgressBar: Sie schreibt direkt auf den Output-Stream und sollte deshalb nicht mit anderen writeln()-Aufrufen während des Fortschritts vermischt werden, da das die Anzeige zerreißen kann. Die übliche Praxis ist, Zwischenmeldungen erst nach finish() auszugeben, oder setMessage() zu verwenden, um zusätzlichen Text direkt in die Fortschrittszeile einzubetten.

6. Exit-Codes und robuste Fehlerbehandlung in CLI-Skripten

Ein oft übersehener, aber für Automatisierung entscheidender Aspekt von CLI-Skripten ist der Exit-Code. Cron-Jobs, CI-Pipelines und Monitoring-Systeme werten üblicherweise ausschließlich den numerischen Rückgabewert eines Prozesses aus, nicht seinen Ausgabetext. Symfony Console definiert dafür in der Command-Klasse drei benannte Konstanten: Command::SUCCESS (0) für einen erfolgreichen Lauf, Command::FAILURE (1) für einen fehlgeschlagenen Lauf und Command::INVALID (2) für ungültige Eingaben oder Aufrufparameter.

Diese Konstanten statt roher Zahlen zu verwenden, macht Commands selbstdokumentierend und verhindert Verwechslungen zwischen verschiedenen Fehlerarten. Ein Skript, das bei fehlender Berechtigung Command::FAILURE zurückgibt, bei einem falschen Parameter aber Command::INVALID, ermöglicht es nachgelagerten Systemen, gezielt zu unterscheiden, ob ein Retry sinnvoll ist oder ob eine Konfigurationsänderung nötig wäre.

Für unerwartete Fehler, etwa eine nicht erreichbare Datenbank oder eine defekte Eingabedatei, sollte execute() Exceptions gezielt abfangen und in einen passenden Exit-Code sowie eine verständliche Fehlermeldung über $io->error() übersetzen, statt die Exception unbehandelt bis zum globalen Handler durchzureichen. Symfony Console fängt zwar auch unbehandelte Exceptions ab und gibt einen Stacktrace sowie Exit-Code 1 aus, doch eine gezielte Behandlung liefert dem Betreiber eine deutlich verständlichere Fehlermeldung als einen rohen Stacktrace.

7. Commands testen mit CommandTester

Die konsequente Trennung von Konfiguration und Ausführung in Command-Klassen zahlt sich bei der Testbarkeit direkt aus. Die Klasse CommandTester aus dem Paket symfony/console erlaubt es, einen Command ohne echten Terminal-Aufruf und ohne laufenden Prozess auszuführen. Sie simuliert Eingaben, fängt die Ausgabe ab und stellt Assertions über Exit-Code und Ausgabetext bereit, sodass CLI-Logik genauso zuverlässig getestet werden kann wie jede andere PHP-Klasse.

Für Commands mit interaktiven Prompts bietet CommandTester::setInputs() die Möglichkeit, eine Liste simulierter Terminaleingaben vorzugeben, die dann nacheinander an ask(), confirm() oder choice() geliefert werden. Dadurch lassen sich auch komplexe interaktive Abläufe deterministisch testen, ohne dass ein echter Anwender am Terminal sitzen muss. Die Methode getDisplay() liefert die vollständige Ausgabe als String zurück, sodass sich Erfolgsmeldungen, Fehlertexte oder Tabellenzeilen gezielt prüfen lassen.

In einer CI-Pipeline bedeutet das: CLI-Skripte werden nicht länger nur manuell im Terminal ausprobiert, sondern laufen bei jedem Commit automatisiert durch PHPUnit. Regressionen, etwa ein versehentlich vertauschtes Argument oder ein falsch berechneter Exit-Code, fallen so auf, bevor sie in Produktion ausgeführt werden.


<?php

declare(strict_types=1);

namespace App\Tests\Command;

use App\Command\ImportCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;

final class ImportCommandTest extends TestCase
{
    public function testImportSucceedsWithValidSource(): void
    {
        $application = new Application();
        $application->add(new ImportCommand());

        $command = $application->find('app:import');
        $tester = new CommandTester($command);

        // Simulate answers for any interactive prompts
        $tester->setInputs(['yes']);

        $exitCode = $tester->execute([
            'source' => 'fixtures/products.csv',
            '--dry-run' => true,
        ]);

        self::assertSame(Command::SUCCESS, $exitCode);
        self::assertStringContainsString('Import finished.', $tester->getDisplay());
    }

    public function testImportFailsWithMissingRequiredArgument(): void
    {
        $application = new Application();
        $application->add(new ImportCommand());

        $tester = new CommandTester($application->find('app:import'));

        // Missing the required "source" argument raises a RuntimeException
        $this->expectException(\RuntimeException::class);
        $tester->execute([]);
    }
}

8. Scheduling und Locking: die Symfony Lock-Component und Cron-Integration

CLI-Skripte, die per Cron oder als Kubernetes-CronJob geplant laufen, bergen ein spezifisches Risiko: Läuft ein Skript ungewöhnlich lange, etwa weil ein externer API-Aufruf hängt, startet der nächste Cron-Trigger den Command möglicherweise erneut, während die vorherige Ausführung noch aktiv ist. Zwei parallele Läufe desselben Import- oder Synchronisations-Commands können zu doppelten Datensätzen, Race Conditions oder inkonsistentem Zustand führen.

Die Symfony Lock-Component, ein weiteres eigenständiges Composer-Paket, löst genau dieses Problem mit einer LockFactory. Ein Command fordert zu Beginn ein Lock für einen eindeutigen Ressourcennamen an. Gelingt der Erwerb nicht, weil bereits ein anderer Prozess dasselbe Lock hält, beendet sich der Command sofort mit einem klaren Hinweis statt parallel weiterzulaufen. Das Lock wird über einen konfigurierbaren Store gehalten, etwa eine Datei, Redis oder eine Datenbanktabelle, wodurch die Lösung auch über mehrere Server hinweg funktioniert.

In der Praxis kombiniert man den Cron-Eintrag mit einer definierten Lock-Strategie im Command selbst. Der Cron-Daemon kümmert sich nur um die zeitliche Auslösung, während Symfony Console zusammen mit der Lock-Component sicherstellt, dass zu jedem Zeitpunkt höchstens eine Instanz des Commands aktiv arbeitet, unabhängig davon, wie oft der Cron-Trigger in der Zwischenzeit ausgelöst wurde.


# crontab entry: run every 5 minutes, output goes to a log file
*/5 * * * * /usr/bin/php /var/www/bin/console app:sync-orders >> /var/log/sync-orders.log 2>&1

<?php

declare(strict_types=1);

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;

final class SyncOrdersCommand extends Command
{
    private LockFactory $lockFactory;

    public function __construct()
    {
        parent::__construct();

        // File-based store keeps this example dependency-free
        $this->lockFactory = new LockFactory(new FlockStore());
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $lock = $this->lockFactory->createLock('app:sync-orders');

        // Non-blocking: fail fast if another instance already holds the lock
        if (!$lock->acquire()) {
            $output->writeln('Another instance is already running, skipping this run.');

            return Command::SUCCESS;
        }

        try {
            $output->writeln('Syncing orders...');
            // Synchronization logic goes here
        } finally {
            $lock->release();
        }

        return Command::SUCCESS;
    }
}

9. Von Skript zu Tool: Packaging als eigenständige PHAR-Datei

Sobald ein CLI-Tool über den eigenen Projektrahmen hinaus verteilt werden soll, etwa als internes Werkzeug für mehrere Teams oder als öffentlich verfügbares Kommandozeilenprogramm, ist eine PHAR-Datei die gängige Verteilungsform. Ein PHAR bündelt alle PHP-Quelldateien, die Composer-Abhängigkeiten und einen Bootstrap-Einstiegspunkt in einer einzigen ausführbaren Datei, die ohne separate vendor-Installation auf jedem System mit passender PHP-Version läuft.

Das Tool box (humbug/box) hat sich als De-facto-Standard für das Bauen von PHARs aus Symfony Console-Anwendungen etabliert. Es liest eine box.json-Konfigurationsdatei, in der Einstiegspunkt, einzuschließende Verzeichnisse und Optimierungen wie Kompression definiert werden, und erzeugt daraus eine einzelne, versionierte PHAR-Datei. Alternativ lässt sich ein PHAR auch direkt mit der eingebauten Phar-Klasse von PHP erzeugen, box übernimmt jedoch typische Fallstricke wie das korrekte Einbinden des Composer-Autoloaders automatisch.

Ein wichtiger Hinweis für Produktionsumgebungen: PHAR-Ausführung erfordert, dass die PHP-Ini-Einstellung phar.readonly nicht das Erstellen neuer PHARs blockiert, während bereits gebaute PHARs in aller Regel unabhängig von dieser Einstellung ausgeführt werden können. Nach dem Bau lässt sich die PHAR-Datei mit chmod +x direkt ausführbar machen und über den PATH systemweit verfügbar machen, wodurch aus dem ursprünglichen CLI-Skript ein vollwertiges, verteilbares Kommandozeilenwerkzeug wird.


# Install box as a dev dependency or as a global tool
composer require --dev humbug/box

# box.json defines entry point, directories and compression
# {
#   "main": "bin/console",
#   "output": "build/tool.phar",
#   "directories": ["src", "vendor"],
#   "compression": "GZ"
# }

# Build the PHAR
php vendor/bin/box compile

# Make it executable and run it directly
chmod +x build/tool.phar
./build/tool.phar app:import fixtures/products.csv

10. Zusammenfassung

Die Symfony-Console-Komponente verwandelt rohe $argv-Skripte in strukturierte, getestete CLI-Werkzeuge, ohne dass das komplette Symfony-Framework installiert werden muss. Command-Klassen trennen Deklaration und Ausführung sauber, InputArgument und InputOption übernehmen Validierung und Hilfetext automatisch, und SymfonyStyle liefert einheitliche, interaktive Prompts mit eingebauter Validierung. Fortschrittsanzeigen und Tabellen machen auch langlaufende Batch-Jobs für Betreiber nachvollziehbar.

Für den produktiven Einsatz sind drei Aspekte entscheidend: klare Exit-Codes über Command::SUCCESS, FAILURE und INVALID für Monitoring und Automatisierung, CommandTester für automatisierte Tests in der CI-Pipeline, und die Symfony Lock-Component, um parallele Ausführungen desselben geplanten Commands zu verhindern. Wer diese Bausteine konsequent nutzt und das fertige Tool bei Bedarf als PHAR packagt, erhält aus einem einfachen CLI-Skript ein robustes, verteilbares Werkzeug für den gesamten Entwicklungs- und Betriebsalltag.

Robuste CLI-Skripte mit Symfony Console - Das Wichtigste auf einen Blick

Command-Struktur

Eine Command-Klasse pro Befehl, configure() für Deklaration, execute() für Logik. Registriert über eine zentrale Application.

Argumente & Prompts

InputArgument und InputOption validieren automatisch. SymfonyStyle liefert Prompts mit Validierungs-Callback.

Tests & Exit-Codes

CommandTester prüft Ausgabe und Status ohne echten Terminal-Aufruf. Command::SUCCESS/FAILURE/INVALID für klare Automatisierung.

Locking & Packaging

Symfony Lock verhindert parallele Cron-Läufe. box baut das fertige Tool zu einer einzigen, verteilbaren PHAR-Datei.

11. FAQ: Robuste CLI-Skripte mit Symfony Console

1Was ist die Symfony-Console-Komponente?
Eine eigenständige Composer-Komponente zum Bauen von PHP-CLI-Anwendungen mit Command-Klassen, Argument-Validierung, Hilfetext, Prompts und Test-Helper, ohne das komplette Symfony-Framework.
2Braucht man das ganze Symfony-Framework dafür?
Nein. composer require symfony/console genügt, das Paket funktioniert unabhängig von Framework-Wahl oder ganz ohne Framework.
3InputArgument vs. InputOption?
InputArgument ist positionsbasiert ohne Präfix. InputOption wird mit --name angegeben und ist standardmäßig optional. Beide unterstützen required, optional und Array-Modi.
4Wie erzwinge ich ein Pflichtargument?
Mit InputArgument::REQUIRED. Fehlt das Argument, bricht Symfony Console vor execute() mit klarer Fehlermeldung ab.
5Was macht SymfonyStyle?
Einheitlicher visueller Stil plus Prompt-Methoden wie ask(), confirm() und choice(), inklusive automatischer Validierungs-Callback-Unterstützung.
6Wie teste ich einen Command ohne Terminal?
Mit CommandTester: führt den Command programmatisch aus, simuliert Eingaben und liefert Exit-Code sowie Ausgabetext für Assertions.
7Welche Exit-Codes gibt es?
Command::SUCCESS (0), Command::FAILURE (1), Command::INVALID (2). Benannte Konstanten statt roher Zahlen für eindeutige Automatisierung.
8Parallele Cron-Läufe verhindern?
Mit der Symfony Lock-Component: LockFactory erstellt ein Lock, gelingt der Erwerb nicht, beendet sich der Command sofort statt parallel weiterzulaufen.
9Fortschritt bei langen Skripten anzeigen?
Mit ProgressBar: initialisiert mit Gesamtanzahl Schritten, weitergeschaltet mit advance(), inklusive verstrichener Zeit und Restdauerschätzung.
10Was ist eine PHAR-Datei?
Eine gebündelte, ausführbare Datei mit PHP-Code, Abhängigkeiten und Einstiegspunkt, üblicherweise mit dem Tool box gebaut, ohne separate vendor-Installation nutzbar.

Mironsoft

PHP-Entwicklung, CLI-Automatisierung und Magento-Expertise

CLI-Skripte, die im Alltag wirklich tragen?

Wir bauen wartbare Automatisierungs-Tools auf Basis von Symfony Console, mit vollständiger Testabdeckung, sauberen Exit-Codes und Locking-Strategien für den produktiven Dauerbetrieb.

Command-Design

Saubere Command-Klassen mit InputArgument, InputOption und aussagekräftigen Exit-Codes

Test-Automatisierung

CommandTester-Suiten in der CI-Pipeline für zuverlässige CLI-Logik

Scheduling & Packaging

Symfony Lock gegen parallele Läufe, PHAR-Builds für verteilbare Tools