Skip to content

chore: modernize code style with PHP 8.2 syntax#9

Merged
ralflang merged 6 commits intoFRAMEWORK_6_0from
feat/modern-api-complete
Mar 7, 2026
Merged

chore: modernize code style with PHP 8.2 syntax#9
ralflang merged 6 commits intoFRAMEWORK_6_0from
feat/modern-api-complete

Conversation

@ralflang
Copy link
Member

@ralflang ralflang commented Mar 7, 2026

Applies PHP CS Fixer modernization: array() → [], const → public const, copyright year updates to 2026. Covers 105 files across entire codebase for consistency with Horde 6 standards.

ralflang added 6 commits March 4, 2026 15:16
Implement immutable help formatter for Modern API following Principle #5 and #7.
Provides professional, terminal-width-aware help text formatting with full
support for options, groups, and descriptions.

Core Components:
- HelpFormatter: Immutable help text generator
- Integration with ImmutableParser via formatHelp()
- Builder pattern for customization
- Terminal width auto-detection

Features:
- Usage line formatting with %prog substitution
- Description and epilog support
- Option formatting with alignment and wrapping
- Option groups with descriptions
- Default value display (e.g., "(default: 8080)")
- Choice list display (e.g., "(choices: 'json', 'xml', 'csv')")
- Metavar support for argument placeholders
- Short/long option ordering control
- Text wrapping for terminal width
- Configurable indentation

Formatter Configuration:
- width: Terminal width (0 = auto-detect, default: 80)
- indent: Indentation increment (default: 2)
- maxHelpPosition: Column before wrapping help text (default: 24)
- shortFirst: Show short option first in listings (default: true)

Formatting Rules:
- Options aligned at configurable column position
- Help text wraps to terminal width
- Default values and choices automatically appended
- Groups separated with headers
- Consistent spacing and indentation

Parser Integration:
- formatHelp(): Generate help text for parser
- Accepts optional custom formatter
- Uses builder-provided formatter if available
- Falls back to default formatter

Example Usage:
  $formatter = HelpFormatter::create()
      ->withWidth(100)
      ->withIndent(4)
      ->build();

  $parser = ParserBuilder::create()
      ->withUsage('%prog [options] <file>')
      ->withDescription('Process files')
      ->addOption($verboseOption)
      ->build();

  echo $parser->formatHelp($formatter);

Output Example:
  Usage: myapp [options] <file>

  Process files

  Options:
    -v, --verbose         Increase verbosity
    -p, --port=PORT       Server port (default: 8080)
    -f, --format=FORMAT   Output format (choices: 'json', 'xml', 'csv')
                          (default: json)

Test Coverage:
- 28 tests added (234 total Modern API tests)
- 73 assertions added (403 total assertions)
- All tests passing (100%)
- Tests cover all formatting scenarios
- Integration tests with ImmutableParser

Formatter Characteristics:
- Readonly class (Principle #7)
- Immutable builder pattern (Principle #5)
- No side effects, pure output generation
- Thread-safe and cacheable
- Fast formatting (no reflection)

Text Wrapping Algorithm:
- Word-based wrapping (no mid-word breaks)
- Respects indentation for wrapped lines
- Handles long option strings gracefully
- Falls back to next-line help for long options

Terminal Width Detection:
- Auto-detects via tput cols if available
- Falls back to 80 columns default
- Can be explicitly set via withWidth()
- Zero width disables wrapping

Principles Demonstrated:
- Principle #5: Modern API is Immutable - formatter fully immutable
- Principle #7: Immutable Throughout - readonly class, builder for config
- Principle #6: Explicit Access - clear method calls, no magic
- Clean separation between formatting logic and parser logic

Next Steps: Modern API is now feature-complete with parsing and help formatting
Implement core context infrastructure for subcommand-style parsing.
Contexts allow command-specific option sets that don't conflict.

New Components:
- ContextConfig: Immutable context configuration
  - Name, aliases, description, usage, help
  - Context-specific options and groups
  - Argument count validation (min/max)
  - Sub-context support for nested commands

- ContextBuilder: Immutable builder for contexts
  - Fluent API: withDescription(), withUsage(), withAliases()
  - Add options with addOption() and addOptions()
  - Argument requirements: requiresArguments(), acceptsArguments()
  - Convenience methods: requiresExactly(), requiresAtLeast(), requiresAtMost()
  - Sub-context nesting with addContext()

- ParseResult: Extended for context support
  - globalOptions: Options available across all contexts
  - contextOptions: Options specific to activated context
  - context: Name of activated context (null if no context)
  - Backward compatible: existing code works unchanged
  - Convenience methods: hasContext(), getContext()
  - Unified access: getOption() checks context then global

- ParserBuilder: Context support
  - addContext() method for adding contexts
  - addContexts() for bulk addition
  - Contexts passed to ImmutableParser constructor

- ImmutableParser: Context infrastructure
  - Accept contexts array in constructor
  - Build context map for O(1) lookup
  - toBuilder() includes contexts
  - Ready for Phase 2 parsing logic

Tests:
- ContextConfigTest: 16 tests, 51 assertions
- ContextBuilderTest: 18 tests, 40 assertions
- ParseResultContextTest: 8 tests, 39 assertions
- All 421 existing tests passing (100% backward compatible)

Total: 42 new tests, 130 new assertions

Example Usage:
```php
$deploy = ContextBuilder::create('deploy')
    ->withDescription('Deploy application')
    ->withAliases(['dep'])
    ->addOption($forceOption)
    ->requiresArguments(1, 'environment')
    ->build();

$parser = ParserBuilder::create()
    ->addOption($globalVerbose)
    ->addContext($deploy)
    ->build();

// Phase 2 will implement actual parsing logic
```

Design Decisions:
- Follows all 8 guiding principles from argv-modernization-design-decisions.md
- Immutable configs (readonly classes)
- Builders are NOT readonly (internal mutability for clone-on-modify)
- Explicit access methods (no ArrayAccess)
- Type-safe with PHP 8.2+ features
- Backward compatible: no breaking changes to existing Modern API
- Context is optional: parsers work with or without contexts

Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
⏳ Phase 2: Context Parsing Logic (Next)
⏳ Phase 3: Help Generation
⏳ Phase 4: Advanced Features
⏳ Phase 5: Polish and Documentation
Implement full context-aware parsing with global and context-specific options.

Context Detection:
- First positional argument checked against registered contexts
- Context name or any alias triggers context activation
- Global options parsed before context detected
- Context-specific options parsed after context activated

Parsing Modes:
1. Legacy Mode (no contexts):
   - Standard POSIX parsing like before
   - All options in result->options
   - 100% backward compatible

2. Context Mode (contexts registered, none active):
   - Parses global options
   - No context detected in arguments
   - Options in result->globalOptions
   - result->context is null

3. Context Mode (context active):
   - Parses global options first
   - Detects context from positional arg
   - Parses context-specific options
   - result->globalOptions + result->contextOptions
   - result->context contains context name

Context Option Lookup:
- Context options checked first when in context
- Falls back to global options if not found in context
- Allows same option name in different contexts without conflict

Argument Validation:
- Validates argument count against context requirements
- Throws InvalidArgumentCountException if count invalid
- Provides clear error messages (exact, at least, at most)

New Exceptions:
- UnknownContextException: Unknown context name provided
- InvalidArgumentCountException: Wrong number of arguments for context

ImmutableParser Changes:
- parse() delegates to parseLegacy() or parseWithContexts()
- parseLegacy(): Original parsing logic (no contexts)
- parseWithContexts(): Two-phase context-aware parsing
- processContextOption(): Parse context-specific options
- findContextOption(): O(1) lookup with partial matching

ParseResult Enhancements:
- getOption() checks context then global (unified access)
- hasOption() checks both context and global
- hasContext(), getContext() for context info
- globalOptions, contextOptions properties

Tests:
- ContextParsingTest: 15 comprehensive tests
  - Basic context detection
  - Context with arguments (single and multiple)
  - Global options parsing
  - Context-specific options parsing
  - Combined global and context options
  - getOption() fallback behavior
  - Context aliases
  - Multiple contexts
  - No context detected (fallback to global)
  - Argument count validation (exact, range)
  - Context option overrides global
  - Backward compatibility (no contexts)

Total: 436 tests, 887 assertions (100% passing)

Example Usage:
```php
$globalVerbose = OptionBuilder::create()
    ->long('--verbose')
    ->flag()
    ->build();

$contextForce = OptionBuilder::create()
    ->long('--force')
    ->flag()
    ->build();

$deployContext = ContextBuilder::create('deploy')
    ->withDescription('Deploy application')
    ->withAliases(['dep'])
    ->addOption($contextForce)
    ->requiresArguments(1, 'environment')
    ->build();

$parser = ParserBuilder::create()
    ->addOption($globalVerbose)
    ->addContext($deployContext)
    ->build();

// Parse: app --verbose deploy --force production
$result = $parser->parse(['--verbose', 'deploy', '--force', 'production']);

// Access results:
$result->getContext();                          // 'deploy'
$result->globalOptions->get('verbose');         // true
$result->contextOptions->get('force');          // true
$result->arguments[0];                          // 'production'
$result->getOption('force');                    // true (checks context)
$result->getOption('verbose');                  // true (fallback to global)
```

Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
✅ Phase 2: Context Parsing Logic (Complete)
⏳ Phase 3: Help Generation (Next)
⏳ Phase 4: Advanced Features
⏳ Phase 5: Polish and Documentation
Add comprehensive help generation for contexts with proper separation
between data structure (Argv) and formatting (HelpFormatter).

HelpFormatter Enhancements:
- formatContexts(): Lists all available contexts with descriptions
- formatContext(): Formats single context name and description
- formatContextHelp(): Complete help for specific context
- formatArgumentRequirements(): Shows argument count requirements
- Handle null prog parameter gracefully (defaults to 'program')

Format Structure:
1. Main Help (parser->formatHelp()):
   - Usage line
   - Description
   - Commands: (contexts list)
   - Global Options:
   - Epilog

2. Context Help (parser->formatContextHelp('context')):
   - Usage: program context [options]
   - Context description
   - Detailed help text
   - Context Options: (context-specific)
   - Global Options: (available in context)
   - Arguments: (requirements)

ImmutableParser API:
- formatHelp(): Generate main help with contexts
- formatContextHelp(name): Generate help for specific context
- getContexts(): Get all registered contexts
- getContext(name): Get specific context by name or alias
- hasContexts(): Check if contexts are registered

Help Content:
- Context names with aliases shown as: deploy (dep, depl)
- Context descriptions from ContextConfig
- Global vs context-specific options clearly separated
- Argument requirements formatted: "Requires exactly 2 arguments: source destination"
- Terminal width-aware text wrapping

Design Philosophy:
- Argv provides data structure and help data
- HelpFormatter handles text formatting
- horde/cli can add colors, interactive features later
- Clean separation of concerns maintained

Tests:
- ContextHelpTest: 11 comprehensive tests
  - Format help with contexts
  - Context aliases in help
  - Context-specific help
  - Invalid context exception
  - Get contexts/context methods
  - hasContexts() method
  - Global and context options separation
  - Argument requirements formatting (exact, range, at least)
  - Backward compatibility (no contexts)
  - Custom formatter

Total: 447 tests, 932 assertions (100% passing)

Example Output:
```
Usage: myapp [options] <command>

Application deployment tool

Commands:
  deploy (dep)      Deploy application to environment
  rollback (roll)   Rollback to previous version

Options:
  -v, --verbose     Verbose output

# Context-specific help:
$ myapp help deploy

Usage: myapp deploy [options] <environment>

Deploy application to environment

Deploys the application to the specified environment with health
checks and rollback capabilities.

Context Options:
  --force           Force deployment
  --timeout INT     Deployment timeout in seconds (default: 60)

Global Options:
  -v, --verbose     Verbose output

Arguments:
  Requires exactly 1 argument: environment name
```

Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
✅ Phase 2: Context Parsing Logic (Complete)
✅ Phase 3: Help Generation (Complete)
⏳ Phase 4: Advanced Features (Optional)
⏳ Phase 5: Polish and Documentation (Optional)

Core feature complete and production-ready!
Apply PHP CS Fixer rules across all source and test files:
- Update copyright years (2017 → 2026)
- Modernize PHP syntax:
  - array() → [] short array syntax
  - const → public const visibility
  - isset() ?: null → ?? null coalescing
  - Cast spacing: (string)$var → (string) $var
- Improve code style:
  - Consistent indentation and spacing
  - Switch case formatting
  - Multi-line function parameters
  - Blank line after opening PHP tag

No functional changes - pure formatting improvements following modern
PHP standards (PER/PSR-12).
Resolved conflicts by preferring modern code style:
- Use imported class names (InvalidArgumentException, Exception) over fully qualified names
- Use single-line empty constructor bodies `{}` over multi-line
- Use space before type casts `(int) $value` over no-space `(int)$value`

All conflicts were stylistic differences between branches with identical functionality.
@ralflang ralflang merged commit 3ba9d9c into FRAMEWORK_6_0 Mar 7, 2026
@ralflang ralflang deleted the feat/modern-api-complete branch March 7, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant