- 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.
487 lines
12 KiB
Markdown
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
|