Command::run() returns void and Console::run() returns void, so the only way for a command to exit non zero is to call CLI::error(), which writes to STDERR and calls exit() itself (src/CLI.php:471-478). A command that fails quietly, or one that wants a specific code (2 for "nothing to do", 3 for "config invalid"), has to call exit() by hand, which makes it untestable in process. That is already visible in the test suite, where ExitCodeTest has to spawn real subprocesses.
It also forces the defined('TESTING') checks currently sprinkled through Console::commandNotFound(), Console::validationFailed() and Help::showCommand() to keep the test run alive.
Options, roughly in order of preference:
- Allow
run() to return int|void and have Console::run() return that code, letting the entrypoint do exit($console->run()). Backward compatible, since existing void commands keep returning 0.
- Add
Command::setExitCode() / Console::getExitCode().
Either one lets CLI::error() keep its current behaviour while giving commands a way to fail without terminating the process, and lets exit codes be asserted without subprocesses.
Command::run()returnsvoidandConsole::run()returnsvoid, so the only way for a command to exit non zero is to callCLI::error(), which writes to STDERR and callsexit()itself (src/CLI.php:471-478). A command that fails quietly, or one that wants a specific code (2 for "nothing to do", 3 for "config invalid"), has to callexit()by hand, which makes it untestable in process. That is already visible in the test suite, whereExitCodeTesthas to spawn real subprocesses.It also forces the
defined('TESTING')checks currently sprinkled throughConsole::commandNotFound(),Console::validationFailed()andHelp::showCommand()to keep the test run alive.Options, roughly in order of preference:
run()to returnint|voidand haveConsole::run()return that code, letting the entrypoint doexit($console->run()). Backward compatible, since existingvoidcommands keep returning 0.Command::setExitCode()/Console::getExitCode().Either one lets
CLI::error()keep its current behaviour while giving commands a way to fail without terminating the process, and lets exit codes be asserted without subprocesses.