Skip to content

Commit d6e3f7b

Browse files
authored
refactor: update Console::run() to allow it be called in command() function (#10080)
* refactor `Console::run()` * deprecate `Boot::initializeConsole()` * call `Console::run()` within `command()` * add changelog * fix phpstan * add more tests for `command()` * revert deprecation on `Boot::initializeConsole()`
1 parent c8ac11d commit d6e3f7b

File tree

10 files changed

+147
-142
lines changed

10 files changed

+147
-142
lines changed

system/Boot.php

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,8 @@ public static function bootSpark(Paths $paths): int
161161
static::autoloadHelpers();
162162

163163
static::initializeCodeIgniter();
164-
$console = static::initializeConsole();
165164

166-
return static::runCommand($console);
165+
return static::runCommand(static::initializeConsole());
167166
}
168167

169168
/**
@@ -424,23 +423,18 @@ protected static function saveConfigCache(FactoriesCache $factoriesCache): void
424423

425424
protected static function initializeConsole(): Console
426425
{
427-
$console = new Console();
428-
429-
// Show basic information before we do anything else.
430-
if (is_int($suppress = array_search('--no-header', $_SERVER['argv'], true))) {
431-
unset($_SERVER['argv'][$suppress]);
432-
$suppress = true;
433-
}
434-
435-
$console->showHeader($suppress);
436-
437-
return $console;
426+
return new Console();
438427
}
439428

440429
protected static function runCommand(Console $console): int
441430
{
442-
$exit = $console->run();
431+
$exitCode = $console->initialize()->run();
432+
433+
if (! is_int($exitCode)) {
434+
@trigger_error(sprintf('Starting with CodeIgniter v4.8.0, commands must return an integer exit code. Last command exited with %s. Defaulting to EXIT_SUCCESS.', get_debug_type($exitCode)), E_USER_DEPRECATED);
435+
$exitCode = EXIT_SUCCESS;
436+
}
443437

444-
return is_int($exit) ? $exit : EXIT_SUCCESS;
438+
return $exitCode;
445439
}
446440
}

system/CLI/Console.php

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,61 @@
1616
use CodeIgniter\CodeIgniter;
1717
use Config\App;
1818
use Config\Services;
19-
use Exception;
2019

2120
/**
22-
* Console
23-
*
2421
* @see \CodeIgniter\CLI\ConsoleTest
2522
*/
2623
class Console
2724
{
25+
private const DEFAULT_COMMAND = 'list';
26+
27+
/**
28+
* @var array<string, string|null>
29+
*/
30+
private array $options = [];
31+
2832
/**
2933
* Runs the current command discovered on the CLI.
3034
*
31-
* @return int|void Exit code
35+
* @param list<string> $tokens
3236
*
33-
* @throws Exception
37+
* @return int|null Exit code or null for legacy commands that don't return an exit code.
3438
*/
35-
public function run()
39+
public function run(array $tokens = [])
3640
{
37-
// Create CLIRequest
38-
$appConfig = config(App::class);
39-
Services::createRequest($appConfig, true);
40-
// Load Routes
41-
service('routes')->loadRoutes();
41+
if ($tokens === []) {
42+
$tokens = service('superglobals')->server('argv', []);
43+
}
44+
45+
$parser = new CommandLineParser($tokens);
46+
47+
$arguments = $parser->getArguments();
48+
$this->options = $parser->getOptions();
49+
50+
$this->showHeader($this->hasParameterOption(['no-header']));
51+
unset($this->options['no-header']);
4252

43-
$params = array_merge(CLI::getSegments(), CLI::getOptions());
44-
$params = $this->parseParamsForHelpOption($params);
45-
$command = array_shift($params) ?? 'list';
53+
if ($this->hasParameterOption(['help'])) {
54+
unset($this->options['help']);
4655

47-
return service('commands')->run($command, $params);
56+
if ($arguments === []) {
57+
$arguments = ['help', self::DEFAULT_COMMAND];
58+
} elseif ($arguments[0] !== 'help') {
59+
array_unshift($arguments, 'help');
60+
}
61+
}
62+
63+
$command = array_shift($arguments) ?? self::DEFAULT_COMMAND;
64+
65+
return service('commands')->run($command, array_merge($arguments, $this->options));
66+
}
67+
68+
public function initialize(): static
69+
{
70+
Services::createRequest(config(App::class), true);
71+
service('routes')->loadRoutes();
72+
73+
return $this;
4874
}
4975

5076
/**
@@ -67,24 +93,18 @@ public function showHeader(bool $suppress = false)
6793
}
6894

6995
/**
70-
* Introspects the `$params` passed for presence of the
71-
* `--help` option.
96+
* Checks whether any of the options are present in the command line.
7297
*
73-
* If present, it will be found as `['help' => null]`.
74-
* We'll remove that as an option from `$params` and
75-
* unshift it as argument instead.
76-
*
77-
* @param array<int|string, string|null> $params
98+
* @param list<string> $options
7899
*/
79-
private function parseParamsForHelpOption(array $params): array
100+
private function hasParameterOption(array $options): bool
80101
{
81-
if (array_key_exists('help', $params)) {
82-
unset($params['help']);
83-
84-
$params = $params === [] ? ['list'] : $params;
85-
array_unshift($params, 'help');
102+
foreach ($options as $option) {
103+
if (array_key_exists($option, $this->options)) {
104+
return true;
105+
}
86106
}
87107

88-
return $params;
108+
return false;
89109
}
90110
}

system/Common.php

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313

1414
use CodeIgniter\Cache\CacheInterface;
15+
use CodeIgniter\CLI\Console;
1516
use CodeIgniter\Config\BaseConfig;
1617
use CodeIgniter\Config\Factories;
1718
use CodeIgniter\Context\Context;
@@ -127,7 +128,7 @@ function command(string $command)
127128
$regexString = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
128129
$regexQuoted = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
129130

130-
$args = [];
131+
$tokens = [];
131132
$length = strlen($command);
132133
$cursor = 0;
133134

@@ -140,9 +141,9 @@ function command(string $command)
140141
if (preg_match('/\s+/A', $command, $match, 0, $cursor)) {
141142
// nothing to do
142143
} elseif (preg_match('/' . $regexQuoted . '/A', $command, $match, 0, $cursor)) {
143-
$args[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
144+
$tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
144145
} elseif (preg_match('/' . $regexString . '/A', $command, $match, 0, $cursor)) {
145-
$args[] = stripcslashes($match[1]);
146+
$tokens[] = stripcslashes($match[1]);
146147
} else {
147148
// @codeCoverageIgnoreStart
148149
throw new InvalidArgumentException(sprintf(
@@ -155,39 +156,21 @@ function command(string $command)
155156
$cursor += strlen($match[0]);
156157
}
157158

158-
/** @var array<int|string, string|null> */
159-
$params = [];
160-
$command = array_shift($args);
161-
$optionValue = false;
162-
163-
foreach ($args as $i => $arg) {
164-
if (mb_strpos($arg, '-') !== 0) {
165-
if ($optionValue) {
166-
// if this was an option value, it was already
167-
// included in the previous iteration
168-
$optionValue = false;
169-
} else {
170-
// add to segments if not starting with '-'
171-
// and not an option value
172-
$params[] = $arg;
173-
}
174-
175-
continue;
176-
}
177-
178-
$arg = ltrim($arg, '-');
179-
$value = null;
180-
181-
if (isset($args[$i + 1]) && mb_strpos($args[$i + 1], '-') !== 0) {
182-
$value = $args[$i + 1];
183-
$optionValue = true;
159+
// Don't show the header as it is not needed when running commands from code.
160+
if (! in_array('--no-header', $tokens, true)) {
161+
if (! in_array('--', $tokens, true)) {
162+
$tokens[] = '--no-header';
163+
} else {
164+
$index = (int) array_search('--', $tokens, true);
165+
array_splice($tokens, $index, 0, '--no-header');
184166
}
185-
186-
$params[$arg] = $value;
187167
}
188168

169+
// Prepend an application name, as Console expects one.
170+
array_unshift($tokens, 'spark');
171+
189172
ob_start();
190-
service('commands')->run($command, $params);
173+
(new Console())->run($tokens);
191174

192175
return ob_get_clean();
193176
}

0 commit comments

Comments
 (0)