Skip to content

Add Command class for structured command building with validation#2

Open
TorstenDittmann wants to merge 4 commits intomainfrom
feat/command-validation
Open

Add Command class for structured command building with validation#2
TorstenDittmann wants to merge 4 commits intomainfrom
feat/command-validation

Conversation

@TorstenDittmann
Copy link
Copy Markdown
Contributor

This PR introduces a new Command class to provide structured command building with per-argument validation, improving security when executing shell commands.

Changes

New Features

  • Utopia\Command class: A new value object for building shell commands safely
    • Constructor takes the executable path
    • add() method for adding arguments with optional validation
    • Support for Utopia\Servers\Validator instances or callable validators
    • toArray() returns argv array for safe execution via proc_open()
    • toString() returns shell-escaped string for display/logging

Updates to Console::execute()

  • Now accepts Command|array|string for the command parameter
  • Command and array execute without shell interpretation (safer)
  • Fixed exit code handling for Command and array execution paths

New Dependency

  • Added utopia-php/servers to require section for the Validator class

Tests

  • Added tests for Command functionality:
    • toArray() returns correct argv structure
    • toString() properly escapes shell arguments
    • Callable validator support
    • Validation failure handling
  • Updated existing tests to use Command instead of raw strings
  • Raw string execution test kept for backward compatibility

Documentation

  • Updated README.md with Command usage examples
  • Documented Console::execute() signature changes

Security Benefits

  • Prevents shell injection by avoiding shell interpretation
  • Allows per-argument validation before execution
  • Provides safe string representation with proper escaping

Backward Compatibility

  • Console::execute() still accepts array and string for backward compatibility
  • All existing tests pass

- Add Utopia\Command class for building shell commands safely
- Support per-argument validation using Utopia\Servers\Validator or callables
- Add toArray() for safe argv execution and toString() for escaped shell strings
- Update Console::execute() to accept Command|array|string
- Fix exit code handling for Command/array execution
- Add tests for Command functionality
- Update README with Command usage examples
- Add utopia-php/servers dependency for Validator class
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 14, 2026

Greptile Summary

This PR introduces a Command value object for argv-based command building with per-argument validation, updates Console::execute() to accept Command|array|string, and fixes a pre-existing bug where the array-command exit-code path always returned 0 (because $status was empty without the string-path shell trick, and the fallback to proc_get_status()['exitcode'] was missing). All three concerns raised in previous review threads — the PHP 8.0/8.1 constraint mismatch, the bool-in-union empty-string confusion, and the missing __toString() — are resolved.

Confidence Score: 5/5

  • Safe to merge — all previously raised P1 concerns are resolved and no new issues were found.
  • All three prior P1 threads are addressed: PHP minimum is now correctly set to 8.2, bool is absent from the union type, and __toString() is implemented. The exit-code fix for array commands is correct (proc_get_status exitcode is read on the first call where running = false, which is the only reliable read per PHP docs). No remaining P0 or P1 issues were identified.
  • No files require special attention.

Important Files Changed

Filename Overview
src/Command.php New Command class implementing Stringable with argv-based building, per-argument validation support, and proper __toString() delegation — all previously raised concerns resolved.
src/Console.php Adds Command type to execute(), converts it to array via toArray(), and fixes the exit-code bug where array-command paths always returned 0 (by falling back to proc_get_status()['exitcode'] when pipe 3 is empty).
tests/Console/ConsoleTest.php Migrates existing tests to use Command, adds focused unit tests for toArray/toString/validator/failure paths, and retains a raw-string backward-compatibility test.
composer.json PHP minimum raised to 8.2 (matching the transitive utopia-php/di constraint) and utopia-php/servers added; the effective runtime floor is now accurate.
.github/workflows/test.yml PHP 8.1 dropped from CI matrix to align with the new 8.2+ minimum; 8.2, 8.3, and nightly remain.
README.md Updated code examples reflect the new Command-based API and the updated execute() signature correctly.

Reviews (4): Last reviewed commit: "Fix exit code handling and regenerate lo..." | Re-trigger Greptile

Comment thread composer.json Outdated
Comment thread src/Command.php Outdated
Comment thread src/Command.php
- Remove bool from union types to avoid confusing error messages
- Update PHP version constraint to >=8.2 (required by transitive dependency)
- Implement Stringable interface with __toString() method
@TorstenDittmann
Copy link
Copy Markdown
Contributor Author

@greptile-apps Thanks for the review! I've addressed all three comments:

  1. PHP version constraint: Updated from >=8.0 to >=8.2 to match the transitive dependency requirement from utopia-php/di

  2. Removed bool from union types: The bool type has been removed from both add() and normalize() methods to avoid the confusing error when false is passed (since (string) false === '')

  3. Implemented Stringable: Added implements Stringable and __toString() method that delegates to toString()

All tests pass and PHPStan shows no errors. Could you please take another look?

- Remove PHP 8.1 from test matrix (now requires PHP 8.2+)
- Regenerate composer.lock to sync with updated PHP version constraint
- Fix Console::execute() to use proc_get_status()['exitcode'] instead of proc_close() return value, which can be -1 on some systems
- Regenerate composer.lock using PHP 8.2 to ensure dependencies are compatible with minimum PHP version
@TorstenDittmann
Copy link
Copy Markdown
Contributor Author

@greptile-apps We've pushed fixes for the CI failures:

  1. Fixed exit code handling: Changed Console::execute() to use proc_get_status()['exitcode'] instead of proc_close() return value. The proc_close() function returns -1 on some systems (like macOS with Herd PHP), which was causing test failures.

  2. Regenerated composer.lock with PHP 8.2: Previously the lock file was generated with PHP 8.4, which pulled in doctrine/instantiator requiring PHP ^8.4. This caused syntax errors on PHP 8.2 CI jobs. The lock file is now generated with PHP 8.2 to ensure all dependencies are compatible with the minimum PHP version.

All tests pass locally now. Could you please take another look?

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