Files
michaelschiemer/docs/console-best-practices.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

487 lines
12 KiB
Markdown

# 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