# Console Command Best Practices Best practices für Console Command Parameter im Custom PHP Framework. ## Grundprinzipien ### 1. **Prefer Typed Parameters over ConsoleInput** ✅ Typed Parameters sind der bevorzugte Weg für Console Commands. **❌ Avoid: Manual parsing mit ConsoleInput** ```php #[ConsoleCommand(name: 'user:create')] public function execute(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode { $email = $input->getArgument('email'); $age = (int) ($input->getArgument('age') ?? 18); // Manuelle Validierung if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { return ExitCode::FAILURE; } } ``` **✅ Prefer: Typed parameters** ```php #[ConsoleCommand(name: 'user:create')] public function execute( ConsoleOutputInterface $output, Email $email, // Value Object validation int $age = 18 // Automatic type conversion ): ExitCode { // Email already validated, age already int } ``` ### 2. **Use Nullable Bool for Optional Flags** ✅ Flags sollten als `?bool` Parameter definiert werden, nicht als reguläre Parameter. **✅ Nullable Bool Flags** ```php #[ConsoleCommand(name: 'deploy')] public function execute( ConsoleOutputInterface $output, string $environment, ?bool $dryRun = null, // --dry-run flag ?bool $force = null, // --force flag ?bool $skipTests = null // --skip-tests flag ): ExitCode { if ($dryRun === true) { $output->writeLine("DRY RUN MODE"); } if ($force === true) { $output->writeLine("Forcing deployment"); } } ``` **Usage:** ```bash # No flags php console.php deploy production # dryRun=null, force=null, skipTests=null # With flags php console.php deploy production --dry-run --force # dryRun=true, force=true, skipTests=null ``` ### 3. **ConsoleOutput only when needed** ✅ `ConsoleOutputInterface` nur verwenden, wenn tatsächlich Output benötigt wird. **✅ With Output** ```php public function execute(ConsoleOutputInterface $output, string $path): ExitCode { $output->writeLine("Processing {$path}..."); // ... processing $output->writeLine("Done!"); return ExitCode::SUCCESS; } ``` **✅ Without Output (Silent)** ```php public function execute(string $path): ExitCode { // Silent processing - no output needed file_put_contents('/tmp/result.txt', $path); return ExitCode::SUCCESS; } ``` ## Parameter Types Übersicht ### Framework Parameters (Auto-Injected) Diese werden automatisch vom Framework bereitgestellt: ```php ConsoleInput $input // Variable args, raw access ConsoleOutputInterface $output // User feedback ``` **Position**: Framework-Parameter können an beliebiger Position stehen. ```php // ✅ All valid positions public function execute(ConsoleOutputInterface $output, string $name): ExitCode { } public function execute(string $name, ConsoleOutputInterface $output): ExitCode { } public function execute(string $name, int $age, ConsoleOutputInterface $output): ExitCode { } ``` ### User Parameters (From Command Line) Diese werden von Command-Line-Argumenten aufgelöst: **Primitive Types:** ```php string $name // String argument int $age // Integer with validation float $amount // Float/decimal number bool $flag // Boolean flag (not recommended, use ?bool) ``` **Nullable Bool for Flags:** ```php ?bool $verbose = null // Optional flag: null=not provided, true=--verbose ?bool $force = null // Optional flag: null=not provided, true=--force ``` **Value Objects:** ```php Email $email // Automatic Email validation Url $website // Automatic URL validation // Any VO with fromString() or __construct(string) ``` **Enums:** ```php enum Environment: string { case DEV = 'development'; case PROD = 'production'; } public function execute(Environment $env): ExitCode ``` **Default Values:** ```php string $name = 'Guest' // Optional with default int $maxResults = 10 // Optional with default ?bool $verbose = null // Optional flag ``` ## Best Practices ### Pattern 1: Simple Command with Required Args ```php #[ConsoleCommand(name: 'user:greet')] public function execute(ConsoleOutputInterface $output, string $name): ExitCode { $output->writeLine("Hello, {$name}!"); return ExitCode::SUCCESS; } ``` **Usage:** `php console.php user:greet Alice` ### Pattern 2: Command with Optional Params ```php #[ConsoleCommand(name: 'user:list')] public function execute( ConsoleOutputInterface $output, int $limit = 10, int $offset = 0 ): ExitCode { $output->writeLine("Showing {$limit} users starting at {$offset}"); return ExitCode::SUCCESS; } ``` **Usage:** - `php console.php user:list` (limit=10, offset=0) - `php console.php user:list 20` (limit=20, offset=0) - `php console.php user:list 20 5` (limit=20, offset=5) ### Pattern 3: Command with Flags ```php #[ConsoleCommand(name: 'deploy')] public function execute( ConsoleOutputInterface $output, string $environment, ?bool $dryRun = null, ?bool $force = null ): ExitCode { $output->writeLine("Deploying to {$environment}"); if ($dryRun === true) { $output->writeLine("DRY RUN MODE"); return ExitCode::SUCCESS; } if ($force === true) { $output->writeLine("FORCING deployment"); } // Actual deployment return ExitCode::SUCCESS; } ``` **Usage:** - `php console.php deploy production` - `php console.php deploy production --dry-run` - `php console.php deploy production --force` - `php console.php deploy production --dry-run --force` ### Pattern 4: Value Objects for Validation ```php #[ConsoleCommand(name: 'send-email')] public function execute( ConsoleOutputInterface $output, Email $to, string $subject ): ExitCode { // Email is already validated! $output->writeLine("Sending email to {$to->value}"); $output->writeLine("Subject: {$subject}"); return ExitCode::SUCCESS; } ``` **Usage:** `php console.php send-email user@example.com "Welcome"` ### Pattern 5: Enums for Type Safety ```php enum LogLevel: string { case DEBUG = 'debug'; case INFO = 'info'; case WARNING = 'warning'; case ERROR = 'error'; } #[ConsoleCommand(name: 'log:show')] public function execute( ConsoleOutputInterface $output, LogLevel $level = LogLevel::INFO ): ExitCode { $output->writeLine("Showing {$level->value} logs"); return ExitCode::SUCCESS; } ``` **Usage:** - `php console.php log:show` (level=info, default) - `php console.php log:show error` - `php console.php log:show debug` ### Pattern 6: Variable Arguments (Special Case) **Only use ConsoleInput when you need:** - Variable number of arguments - Raw command-line access - Dynamic argument handling ```php #[ConsoleCommand(name: 'file:process')] public function execute( ConsoleInput $input, ConsoleOutputInterface $output ): ExitCode { $files = $input->getArguments(); if (empty($files)) { $output->error('No files provided'); return ExitCode::FAILURE; } foreach ($files as $file) { $output->writeLine("Processing {$file}"); } return ExitCode::SUCCESS; } ``` **Usage:** `php console.php file:process file1.txt file2.txt file3.txt ...` ## When to Use What ### Use Typed Parameters (90% of commands) - ✅ Fixed number of arguments - ✅ Known parameter types - ✅ Type validation needed - ✅ Value Objects or Enums - ✅ Optional parameters with defaults ### Use ConsoleInput (10% of commands) - ✅ Variable number of arguments - ✅ Many optional flags (>5) - ✅ Raw command-line access needed - ✅ Dynamic argument handling - ✅ Argument count unknown at design time ### Use ConsoleOutput - ✅ Always when user feedback needed - ✅ Progress indication - ✅ Error messages - ✅ Status updates - ❌ Not needed for silent commands ### Use Nullable Bool Flags - ✅ Always for optional flags - ✅ Three-state logic (null/false/true) - ✅ Clean syntax: `--verbose` instead of `--verbose=true` - ✅ Multiple independent flags ## Migration Guide ### Old Style → New Style **Before (Old Style):** ```php #[ConsoleCommand(name: 'user:create')] public function execute(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode { $email = $input->getArgument('email') ?? throw new \Exception('Email required'); $age = (int) ($input->getArgument('age') ?? 18); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $output->error('Invalid email'); return ExitCode::FAILURE; } $output->writeLine("Creating user: {$email}, age: {$age}"); return ExitCode::SUCCESS; } ``` **After (New Style):** ```php #[ConsoleCommand(name: 'user:create')] public function execute( ConsoleOutputInterface $output, Email $email, int $age = 18 ): ExitCode { // Email already validated, age already int $output->writeLine("Creating user: {$email->value}, age: {$age}"); return ExitCode::SUCCESS; } ``` **Benefits:** - ✅ Type safety - ✅ Automatic validation - ✅ Less boilerplate - ✅ Better IDE support - ✅ Self-documenting ### Adding Flags **Before:** ```php public function execute(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode { $dryRun = $input->hasOption('dry-run'); $force = $input->hasOption('force'); if ($dryRun) { $output->writeLine("DRY RUN"); } } ``` **After:** ```php public function execute( ConsoleOutputInterface $output, ?bool $dryRun = null, ?bool $force = null ): ExitCode { if ($dryRun === true) { $output->writeLine("DRY RUN"); } } ``` ## Testing Commands ### Testing with Typed Parameters ```php it('creates user with typed parameters', function () { $method = new ReflectionMethod(UserCommand::class, 'execute'); $resolver = new CommandParameterResolver(new MethodSignatureAnalyzer()); $output = new ConsoleOutput(); // Test with required args $params = $resolver->resolveParameters( $method, ['alice@example.com', '25'], // email, age null, $output ); expect($params[0])->toBeInstanceOf(ConsoleOutputInterface::class); expect($params[1])->toBeInstanceOf(Email::class); expect($params[2])->toBe(25); }); ``` ### Testing Flags ```php it('handles nullable bool flags correctly', function () { $method = new ReflectionMethod(DeployCommand::class, 'execute'); // Without flags $params = $resolver->resolveParameters( $method, ['production'], // environment only null, $output ); expect($params[2])->toBeNull(); // dryRun expect($params[3])->toBeNull(); // force // With flags $params = $resolver->resolveParameters( $method, ['production', '--dry-run', '--force'], null, $output ); expect($params[2])->toBeTrue(); // dryRun expect($params[3])->toBeTrue(); // force }); ``` ## Examples Siehe `src/Framework/Console/Examples/ConsoleParameterBestPracticesCommand.php` für vollständige Beispiele aller Patterns. **Run examples:** ```bash docker exec php php console.php best:typed username docker exec php php console.php best:flags production --verbose --force docker exec php php console.php best:mixed Alice 25 --verified --active docker exec php php console.php best:complex production --dry-run --skip-tests ``` ## Summary | Pattern | Use Case | Example | |---------|----------|---------| | **Typed Parameters** | Standard (90%) | `string $name, int $age = 18` | | **Nullable Bool Flags** | Optional flags | `?bool $verbose = null` | | **Value Objects** | Validation | `Email $email, Url $website` | | **Enums** | Fixed choices | `Environment $env` | | **ConsoleInput** | Variable args (10%) | `ConsoleInput $input` | | **ConsoleOutput** | User feedback | `ConsoleOutputInterface $output` | **Remember:** - ✅ Typed parameters = type safety + validation - ✅ Nullable bool = clean flag syntax - ✅ Value Objects = automatic validation - ✅ ConsoleInput only for special cases - ✅ Framework parameters are optional and flexible